Rolling Release #1163
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Rolling Release | |
| on: | |
| workflow_run: | |
| workflows: ["Format Check", "Lint", "Tests", "Build"] | |
| types: [completed] | |
| permissions: | |
| contents: write | |
| concurrency: | |
| # Cancel earlier rolling runs for the same source branch | |
| group: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref_name || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| build-test-release: | |
| outputs: | |
| NEXT_TAG: ${{ steps.rolling_tag.outputs.NEXT_TAG }} | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: ubuntu-x64 | |
| runner: ubuntu-latest | |
| shell: bash | |
| cmake_arch: x86_64 | |
| - name: ubuntu-arm64 | |
| runner: ubuntu-24.04-arm | |
| shell: bash | |
| cmake_arch: aarch64 | |
| - name: macos-x64 | |
| runner: macos-13 | |
| shell: bash | |
| cmake_arch: x86_64 | |
| - name: macos-arm64 | |
| runner: macos-14 | |
| shell: bash | |
| cmake_arch: arm64 | |
| - name: windows-x64 | |
| runner: windows-latest | |
| shell: powershell | |
| cmake_arch: x64 | |
| - name: windows-arm64 | |
| runner: windows-11-arm | |
| shell: powershell | |
| cmake_arch: arm64 | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: "Gate: require format/lint/tests/build success" | |
| id: gate | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| BRANCH: ${{ github.event.workflow_run.head_branch }} | |
| REQUIRED: '["Format Check","Lint","Tests","Build"]' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| repo='${{ github.repository }}' | |
| total=$(echo "$REQUIRED" | jq 'length') | |
| ok=0 | |
| for name in $(echo "$REQUIRED" | jq -r '.[]'); do | |
| wid=$(curl -s -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/$repo/actions/workflows" | jq -r --arg n "$name" '.workflows[] | select(.name==$n) | .id' | head -n1) | |
| if [ -z "$wid" ] || [ "$wid" = "null" ]; then | |
| echo "workflow '$name' not found" >&2 | |
| continue | |
| fi | |
| conclusion=$(curl -s -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/$repo/actions/workflows/$wid/runs?per_page=50&branch=$BRANCH" | \ | |
| jq -r --arg sha "$HEAD_SHA" '.workflow_runs[] | select(.head_sha==$sha) | .conclusion' | head -n1) | |
| if [ "$conclusion" = "success" ]; then | |
| ok=$((ok+1)) | |
| else | |
| echo "workflow '$name' not successful yet (conclusion=${conclusion:-none})" | |
| fi | |
| done | |
| ready=false | |
| if [ "$ok" -eq "$total" ]; then ready=true; fi | |
| echo "ready=$ready" >> "$GITHUB_OUTPUT" | |
| - name: Check recent releases | |
| id: check_releases | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| shell: bash | |
| run: | | |
| SINCE="$(python3 -c 'from datetime import datetime, timedelta; print((datetime.utcnow() - timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%SZ"))')" | |
| COUNT=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ | |
| "https://api.github.com/repos/${{ github.repository }}/releases?per_page=100" \ | |
| | jq "[.[] | select(.created_at >= \"$SINCE\")] | length") | |
| echo "releases_last24h=$COUNT" >> "$GITHUB_OUTPUT" | |
| if [ "$COUNT" -ge 2 ]; then | |
| echo "skip_build=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "skip_build=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Skip build when release limit reached | |
| if: steps.check_releases.outputs.skip_build == 'true' | |
| run: | | |
| echo 'Skipping build: daily release threshold reached.' | |
| - name: Set up C++ build environment | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' | |
| uses: aminya/setup-cpp@v1 | |
| - name: Enable ccache | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| uses: hendrikmuhs/ccache-action@v1.2.19 | |
| with: | |
| key: ${{ runner.os }}-${{ matrix.name }}-rolling-ccache-${{ github.event.workflow_run.head_sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.name }}-rolling-ccache- | |
| - name: Cache CMake dependencies | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' | |
| uses: actions/cache@v4 | |
| with: | |
| path: build/**/_deps | |
| key: ${{ runner.os }}-cmake-${{ hashFiles('CMakeLists.txt', 'cmake/**', '**/CMakeLists.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cmake- | |
| - name: Configure (Linux/macOS) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| shell: bash | |
| env: | |
| CMAKE_CXX_COMPILER_LAUNCHER: ccache | |
| run: | | |
| set -euo pipefail | |
| if [[ "${RUNNER_OS}" == "Linux" ]]; then | |
| export CC=gcc CXX=g++ | |
| else | |
| export CC=clang CXX=clang++ | |
| fi | |
| unset CFLAGS CXXFLAGS LDFLAGS | |
| cmake --preset rolling -DCODEX_FORCE_NO_NSEC=ON | |
| - name: Resolve binary directory (Linux/macOS) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| BIN_ROOT="build/rolling" | |
| if [ ! -d "$BIN_ROOT" ]; then | |
| echo "Binary root not found: $BIN_ROOT" >&2 | |
| exit 1 | |
| fi | |
| BIN_DIR=$(find "$BIN_ROOT" -mindepth 1 -maxdepth 1 -type d | sort | head -n 1) | |
| if [ -z "$BIN_DIR" ]; then | |
| echo "No configure directory found under $BIN_ROOT" >&2 | |
| exit 1 | |
| fi | |
| echo "Resolved binary directory: $BIN_DIR" | |
| echo "ROLLING_BIN_DIR=$BIN_DIR" >> "$GITHUB_ENV" | |
| - name: Build (Linux/macOS) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| shell: bash | |
| run: cmake --build --preset rolling --parallel | |
| - name: Run tests (Linux/macOS) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| : "${ROLLING_BIN_DIR:?ROLLING_BIN_DIR must be set by the resolve step}" | |
| ctest --test-dir "$ROLLING_BIN_DIR" -C RelWithDebInfo --output-on-failure --parallel | |
| - name: Configure (Windows) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os == 'Windows' | |
| shell: powershell | |
| run: | | |
| $arch = '${{ matrix.cmake_arch }}' | |
| $generatorArgs = if ($arch -eq 'arm64') { '-A ARM64' } else { '-A x64' } | |
| cmake -S . -B build -G "Visual Studio 17 2022" $generatorArgs -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCODEX_FORCE_NO_NSEC=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON | |
| - name: Build (Windows) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os == 'Windows' | |
| shell: powershell | |
| run: cmake --build build --config RelWithDebInfo --parallel | |
| - name: Run tests (Windows) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os == 'Windows' | |
| shell: powershell | |
| continue-on-error: true | |
| run: ctest --test-dir build -C RelWithDebInfo --output-on-failure | |
| - name: Stage artifact (Linux/macOS) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| : "${ROLLING_BIN_DIR:?ROLLING_BIN_DIR must be set by the resolve step}" | |
| DEST="dist/${{ matrix.name }}" | |
| mkdir -p "$DEST" | |
| install -m 755 "$ROLLING_BIN_DIR/autogitpull" "$DEST/autogitpull-${{ matrix.name }}" | |
| install -m 644 readme.md "$DEST/README.md" | |
| install -m 644 license.md "$DEST/LICENSE.md" | |
| - name: Stage artifact (Windows) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os == 'Windows' | |
| shell: powershell | |
| run: | | |
| $dest = "dist/${{ matrix.name }}" | |
| New-Item -ItemType Directory -Force -Path $dest | Out-Null | |
| Copy-Item -Path "build/RelWithDebInfo/autogitpull.exe" -Destination (Join-Path $dest 'autogitpull-${{ matrix.name }}.exe') -Force | |
| Copy-Item -Path readme.md -Destination (Join-Path $dest 'README.md') -Force | |
| Copy-Item -Path license.md -Destination (Join-Path $dest 'LICENSE.md') -Force | |
| - name: Upload artifact | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: autogitpull-${{ matrix.name }} | |
| path: dist/${{ matrix.name }}/* | |
| - name: Get next rolling tag (*nix) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| id: rolling_tag | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| TODAY=$(date +'%y.%m.%d') | |
| NEXT="rolling-v${TODAY}" | |
| echo "NEXT_TAG=$NEXT" >> "$GITHUB_ENV" | |
| echo "NEXT_TAG=$NEXT" >> "$GITHUB_OUTPUT" | |
| echo "Rolling tag: $NEXT" | |
| - name: Get next rolling tag (Windows) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os == 'Windows' | |
| id: rolling_tag_win | |
| shell: powershell | |
| run: | | |
| $today = Get-Date -Format "yy.MM.dd" | |
| $next = "rolling-v$today" | |
| "NEXT_TAG=$next" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 | |
| "NEXT_TAG=$next" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8 | |
| Write-Host "Rolling tag: $next" | |
| - name: Tag commit (*nix) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| git config user.name github-actions | |
| git config user.email github-actions@github.com | |
| git tag "$NEXT_TAG" | |
| git push origin "$NEXT_TAG" | |
| - name: Tag commit (Windows) | |
| if: steps.check_releases.outputs.skip_build != 'true' && steps.gate.outputs.ready == 'true' && runner.os == 'Windows' | |
| shell: powershell | |
| run: | | |
| git config user.name github-actions | |
| git config user.email github-actions@github.com | |
| git tag $env:NEXT_TAG | |
| git push origin $env:NEXT_TAG | |
| publish: | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' && needs.build-test-release.outputs.NEXT_TAG != '' }} | |
| needs: build-test-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.build-test-release.outputs.NEXT_TAG }} | |
| name: ${{ needs.build-test-release.outputs.NEXT_TAG }} | |
| body: Automated rolling release ${{ needs.build-test-release.outputs.NEXT_TAG }} | |
| draft: false | |
| prerelease: true | |
| files: | | |
| dist/**/autogitpull-* | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |