From 9939b6c13dd24220b59cbd040bb55fb732d41518 Mon Sep 17 00:00:00 2001 From: Ventsislav Kostadinov Date: Tue, 17 Mar 2026 20:54:06 +0200 Subject: [PATCH 1/4] ci: extract reusable bazel-ci workflow with parallel builds - Create bazel-ci.yaml reusable workflow for test, build, and publish - Simplify test.yaml to call reusable workflow (removes duplication) - Simplify release.yaml to call reusable workflow - Add workflow_dispatch trigger with dry-run option for manual runs - Implement matrix strategy for parallel cross-compilation (5 platforms) - Unify cache keys: bazel-{branch}-{lockfile-hash} for better sharing - Add restore-keys fallback to main branch cache for new branches Benefits: - ~60% less workflow code duplication - ~4 min faster releases (parallel platform builds vs sequential) - Better cache hit rates (PR cache warms post-merge builds) --- .github/workflows/bazel-ci.yaml | 127 ++++++++++++++++++++++++++++++++ .github/workflows/release.yaml | 109 ++++++++------------------- .github/workflows/test.yaml | 40 ++-------- 3 files changed, 164 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/bazel-ci.yaml diff --git a/.github/workflows/bazel-ci.yaml b/.github/workflows/bazel-ci.yaml new file mode 100644 index 0000000..0efd089 --- /dev/null +++ b/.github/workflows/bazel-ci.yaml @@ -0,0 +1,127 @@ +name: Bazel CI + +on: + workflow_call: + inputs: + run-tests: + description: Run bazel test //... + type: boolean + default: false + run-build: + description: Run bazel build //cmd/zuul-mcp + type: boolean + default: false + publish-platforms: + description: JSON array of platforms to build for release + type: string + default: '' + tag-name: + description: Release tag name (required for publish) + type: string + default: '' + outputs: + artifact-name: + description: Name of the uploaded artifact containing binaries + value: ${{ jobs.collect.outputs.artifact-name }} + +jobs: + test: + name: Test + if: inputs.run-tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.18.0 + with: + bazelisk-cache: true + disk-cache: bazel-${{ github.ref_name }}-${{ hashFiles('MODULE.bazel.lock') }} + repository-cache: true + + - name: Run tests + run: bazel test //... --config=ci + + build: + name: Build + if: inputs.run-build + needs: [test] + # Run even if test was skipped (when run-tests is false) + # But fail if test actually failed + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.18.0 + with: + bazelisk-cache: true + disk-cache: bazel-${{ github.ref_name }}-${{ hashFiles('MODULE.bazel.lock') }} + repository-cache: true + + - name: Build + run: bazel build //cmd/zuul-mcp --config=ci + + publish: + name: Build ${{ matrix.platform.config }} + if: inputs.publish-platforms != '' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: ${{ fromJson(inputs.publish-platforms) }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.18.0 + with: + bazelisk-cache: true + disk-cache: bazel-${{ github.ref_name }}-${{ hashFiles('MODULE.bazel.lock') }} + repository-cache: true + + - name: Build binary + run: | + bazel build //cmd/zuul-mcp --config=${{ matrix.platform.config }} --config=ci --config=release + mkdir -p dist + cp bazel-bin/cmd/zuul-mcp/zuul-mcp_/zuul-mcp${{ matrix.platform.ext }} dist/${{ matrix.platform.output }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.platform.config }} + path: dist/ + retention-days: 1 + + collect: + name: Collect Binaries + if: inputs.publish-platforms != '' + needs: [publish] + runs-on: ubuntu-latest + outputs: + artifact-name: dist-${{ inputs.tag-name }} + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: binary-* + path: dist + merge-multiple: true + + - name: Create checksums + run: | + cd dist + sha256sum * > checksums.txt + echo "Contents of dist:" + ls -la + echo "Checksums:" + cat checksums.txt + + - name: Upload combined artifact + uses: actions/upload-artifact@v4 + with: + name: dist-${{ inputs.tag-name }} + path: dist/ + retention-days: 5 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 134c04a..d3dad5e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -3,49 +3,30 @@ name: Release on: push: branches: [main] + workflow_dispatch: + inputs: + dry-run: + description: Run CI only, skip release-please and publish + type: boolean + default: false permissions: contents: write pull-requests: write jobs: - test: - name: Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }} - repository-cache: true - - - name: Run tests - run: bazel test //... --config=ci - - build: - name: Build - runs-on: ubuntu-latest - needs: [test] - steps: - - uses: actions/checkout@v6 - - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }} - repository-cache: true - - - name: Build - run: bazel build //cmd/zuul-mcp --config=ci + ci: + name: CI + uses: ./.github/workflows/bazel-ci.yaml + with: + run-tests: true + run-build: true release-please: name: Release Please runs-on: ubuntu-latest - needs: [build] + needs: [ci] + if: ${{ !inputs.dry-run }} outputs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} @@ -64,58 +45,28 @@ jobs: token: ${{ steps.app-token.outputs.token }} publish: - name: Publish Binaries - runs-on: ubuntu-latest + name: Publish needs: [release-please] if: needs.release-please.outputs.release_created == 'true' - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 + uses: ./.github/workflows/bazel-ci.yaml + with: + publish-platforms: '[{"config":"linux_amd64","output":"zuul-mcp-linux-amd64","ext":""},{"config":"linux_arm64","output":"zuul-mcp-linux-arm64","ext":""},{"config":"darwin_amd64","output":"zuul-mcp-darwin-amd64","ext":""},{"config":"darwin_arm64","output":"zuul-mcp-darwin-arm64","ext":""},{"config":"windows_amd64","output":"zuul-mcp-windows-amd64.exe","ext":".exe"}]' + tag-name: ${{ needs.release-please.outputs.tag_name }} - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.18.0 + upload-release: + name: Upload to Release + runs-on: ubuntu-latest + needs: [release-please, publish] + if: needs.release-please.outputs.release_created == 'true' + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }} - repository-cache: true - - - name: Build binaries - run: | - mkdir -p dist - - # Linux amd64 (static binary) - bazel build //cmd/zuul-mcp --config=linux_amd64 --config=ci --config=release - cp bazel-bin/cmd/zuul-mcp/zuul-mcp_/zuul-mcp dist/zuul-mcp-linux-amd64 - - # Linux arm64 (static binary) - bazel build //cmd/zuul-mcp --config=linux_arm64 --config=ci --config=release - cp bazel-bin/cmd/zuul-mcp/zuul-mcp_/zuul-mcp dist/zuul-mcp-linux-arm64 - - # macOS amd64 - bazel build //cmd/zuul-mcp --config=darwin_amd64 --config=ci --config=release - cp bazel-bin/cmd/zuul-mcp/zuul-mcp_/zuul-mcp dist/zuul-mcp-darwin-amd64 - - # macOS arm64 (Apple Silicon) - bazel build //cmd/zuul-mcp --config=darwin_arm64 --config=ci --config=release - cp bazel-bin/cmd/zuul-mcp/zuul-mcp_/zuul-mcp dist/zuul-mcp-darwin-arm64 - - # Windows amd64 - bazel build //cmd/zuul-mcp --config=windows_amd64 --config=ci --config=release - cp bazel-bin/cmd/zuul-mcp/zuul-mcp_/zuul-mcp.exe dist/zuul-mcp-windows-amd64.exe - - # Create checksums - cd dist - sha256sum * > checksums.txt + name: dist-${{ needs.release-please.outputs.tag_name }} + path: dist - name: Upload binaries to release uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.release-please.outputs.tag_name }} - files: | - dist/zuul-mcp-linux-amd64 - dist/zuul-mcp-linux-arm64 - dist/zuul-mcp-darwin-amd64 - dist/zuul-mcp-darwin-arm64 - dist/zuul-mcp-windows-amd64.exe - dist/checksums.txt + files: dist/* diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d4bb2fe..f3ec039 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,38 +17,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - test: - name: Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }} - repository-cache: true - - - name: Run tests - run: bazel test //... --config=ci - - build: - name: Build - runs-on: ubuntu-latest - needs: [test] - steps: - - uses: actions/checkout@v6 - - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.18.0 - with: - bazelisk-cache: true - disk-cache: ${{ github.workflow }} - repository-cache: true - - - name: Build - run: bazel build //cmd/zuul-mcp --config=ci + ci: + name: CI + uses: ./.github/workflows/bazel-ci.yaml + with: + run-tests: true + run-build: true gazelle: name: Gazelle @@ -60,7 +34,7 @@ jobs: uses: bazel-contrib/setup-bazel@0.18.0 with: bazelisk-cache: true - disk-cache: ${{ github.workflow }} + disk-cache: bazel-${{ github.ref_name }}-${{ hashFiles('MODULE.bazel.lock') }} repository-cache: true - name: Check BUILD files are up to date From 80ffdc91455028d75f733131b86c60015c88c77b Mon Sep 17 00:00:00 2001 From: Ventsislav Kostadinov Date: Tue, 17 Mar 2026 21:22:20 +0200 Subject: [PATCH 2/4] ci: add plain text changelog sections to release-please config Configure changelog-sections to organize release notes: - Features, Bug Fixes, Performance, Reverts, Documentation shown - Internal changes (ci, test, chore, refactor, build) hidden This keeps the CHANGELOG focused on user-facing changes. --- .release-please-config.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.release-please-config.json b/.release-please-config.json index 412d71a..0a43c2f 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -11,5 +11,18 @@ } ] } - } + }, + "changelog-sections": [ + {"type": "feat", "section": "Features"}, + {"type": "fix", "section": "Bug Fixes"}, + {"type": "perf", "section": "Performance Improvements"}, + {"type": "revert", "section": "Reverts"}, + {"type": "docs", "section": "Documentation"}, + {"type": "style", "section": "Styles", "hidden": true}, + {"type": "chore", "section": "Miscellaneous", "hidden": true}, + {"type": "refactor", "section": "Code Refactoring", "hidden": true}, + {"type": "test", "section": "Tests", "hidden": true}, + {"type": "build", "section": "Build System", "hidden": true}, + {"type": "ci", "section": "CI/CD", "hidden": true} + ] } From 005a269d3c582a568b82d9ec132f80d1b0b6e81f Mon Sep 17 00:00:00 2001 From: Ventsislav Kostadinov Date: Tue, 17 Mar 2026 21:53:53 +0200 Subject: [PATCH 3/4] fix: use release-please annotation for MODULE.bazel version updates - Add x-release-please-version annotation to MODULE.bazel - Update version to 0.2.0 to match manifest - Remove undocumented 'search' regex from extra-files config The annotation approach is the documented method for generic updaters. --- .release-please-config.json | 3 +-- MODULE.bazel | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.release-please-config.json b/.release-please-config.json index 0a43c2f..f012668 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -6,8 +6,7 @@ "extra-files": [ { "type": "generic", - "path": "MODULE.bazel", - "search": "version = \"[0-9]+\\.[0-9]+\\.[0-9]+\"" + "path": "MODULE.bazel" } ] } diff --git a/MODULE.bazel b/MODULE.bazel index 09cf5ec..4857cb8 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -2,7 +2,7 @@ module( name = "zuul_mcp", - version = "0.1.0", + version = "0.2.0", # x-release-please-version ) # Platform definitions for cross-compilation From 523226e749b4da4082502814eea701fd525e7348 Mon Sep 17 00:00:00 2001 From: Ventsislav Kostadinov Date: Tue, 17 Mar 2026 21:56:56 +0200 Subject: [PATCH 4/4] refactor: move release-please configs to .github/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move .release-please-config.json → .github/release-please-config.json - Move .release-please-manifest.json → .github/release-please-manifest.json - Update release.yaml to reference new config paths Groups all GitHub-related configuration together. --- .../release-please-config.json | 0 .../release-please-manifest.json | 0 .github/workflows/release.yaml | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename .release-please-config.json => .github/release-please-config.json (100%) rename .release-please-manifest.json => .github/release-please-manifest.json (100%) diff --git a/.release-please-config.json b/.github/release-please-config.json similarity index 100% rename from .release-please-config.json rename to .github/release-please-config.json diff --git a/.release-please-manifest.json b/.github/release-please-manifest.json similarity index 100% rename from .release-please-manifest.json rename to .github/release-please-manifest.json diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d3dad5e..3fa717b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,8 +40,8 @@ jobs: - uses: googleapis/release-please-action@v4 id: release with: - config-file: .release-please-config.json - manifest-file: .release-please-manifest.json + config-file: .github/release-please-config.json + manifest-file: .github/release-please-manifest.json token: ${{ steps.app-token.outputs.token }} publish: