Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 69 additions & 11 deletions .github/workflows/bzlmod-lock-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,33 @@
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************

# Verifies that MODULE.bazel and MODULE.bazel.lock are consistent and up to date.
#
# Two checks run in parallel:
# bzlmod-tidy-check — runs `bazel mod tidy` and fails if it would change any file
# bzlmod-lockfile-check — runs `bazel mod deps --lockfile_mode=error` and fails if the
# lockfile does not match the resolved dependency graph
#
# Recommended alternative: run these checks locally via pre-commit so issues are caught
# before they reach CI. Add the following to your .pre-commit-config.yaml:
#
# - repo: local
# hooks:
# - id: bzlmod-tidy
# name: bazel mod tidy
# entry: bazel mod tidy
# language: system
# pass_filenames: false
# - id: bzlmod-lockfile
# name: bazel mod deps lockfile check
# entry: bazel mod deps --lockfile_mode=error
# language: system
# pass_filenames: false
#
# Security note: this workflow refuses to run on pull_request_target. That event has
# write access to repository secrets while checking out fork code, making it unsafe
# to execute Bazel on untrusted input. Use pull_request or schedule instead.

name: Bazel Bzlmod Lockfile Check

on:
Expand All @@ -23,14 +50,49 @@ on:
type: string

jobs:
bzlmod-lock-check:
bzlmod-tidy-check:
runs-on: ${{ vars.runner_labels_ghub_standard_x64 && fromJSON(vars.runner_labels_ghub_standard_x64) || vars.REPO_RUNNER_LABELS && fromJSON(vars.REPO_RUNNER_LABELS) || 'ubuntu-latest' }}
steps:
- name: Checkout repository (Handle all events)
- name: Refuse to run on pull_request_target
if: github.event_name == 'pull_request_target'
run: |
echo "This workflow must not be called from pull_request_target."
echo "That event has write access to repo secrets while checking out fork code,"
echo "making it unsafe to run bazel on untrusted input."
echo "Use pull_request or schedule instead."
exit 1

- name: Checkout repository
uses: actions/checkout@v4.2.2

- name: Setup Bazel with shared caching
uses: bazel-contrib/setup-bazel@0.18.0
with:
ref: ${{ github.head_ref || github.event.pull_request.head.ref || github.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
disk-cache: false
repository-cache: false
bazelisk-cache: true
cache-save: ${{ github.event_name == 'push' }}

- name: Check MODULE.bazel formatting
working-directory: ${{ inputs.working-directory }}
run: |
bazel mod tidy
git diff --exit-code

bzlmod-lockfile-check:
runs-on: ${{ vars.runner_labels_ghub_standard_x64 && fromJSON(vars.runner_labels_ghub_standard_x64) || vars.REPO_RUNNER_LABELS && fromJSON(vars.REPO_RUNNER_LABELS) || 'ubuntu-latest' }}
steps:
- name: Refuse to run on pull_request_target
if: github.event_name == 'pull_request_target'
run: |
echo "This workflow must not be called from pull_request_target."
echo "That event has write access to repo secrets while checking out fork code,"
echo "making it unsafe to run bazel on untrusted input."
echo "Use pull_request or schedule instead."
exit 1

- name: Checkout repository
uses: actions/checkout@v4.2.2

- name: Setup Bazel with shared caching
uses: bazel-contrib/setup-bazel@0.18.0
Expand All @@ -40,7 +102,7 @@ jobs:
bazelisk-cache: true
cache-save: ${{ github.event_name == 'push' }}

- name: Verify MODULE.bazel.lock exists
- name: Verify MODULE.bazel and MODULE.bazel.lock exist
working-directory: ${{ inputs.working-directory }}
run: |
if [ ! -f "MODULE.bazel" ]; then
Expand All @@ -53,10 +115,6 @@ jobs:
exit 1
fi

- name: Run bazel mod tidy
working-directory: ${{ inputs.working-directory }}
run: bazel mod tidy

- name: Check lock consistency
- name: Check lockfile is up to date
working-directory: ${{ inputs.working-directory }}
run: git diff --exit-code -- MODULE.bazel MODULE.bazel.lock
run: bazel mod deps --lockfile_mode=error
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,29 @@ jobs:

### **16. Bzlmod Lockfile Check Workflow**

This workflow keeps Bazel's lockfile in sync with your module declarations. It records the exact set of resolved module versions and extension outputs so builds are reproducible across machines. The job fails if the lockfile is missing or out of date after running `bazel mod tidy`, which means someone changed `MODULE.bazel` without updating the lockfile.
This workflow keeps `MODULE.bazel` and `MODULE.bazel.lock` consistent and reproducible. Two checks run in parallel:

- **`bzlmod-tidy-check`** — runs `bazel mod tidy` and fails if it would change any file, meaning `MODULE.bazel` has formatting or dependency declarations that are not normalized
- **`bzlmod-lockfile-check`** — runs `bazel mod deps --lockfile_mode=error` and fails if the committed lockfile does not match the resolved dependency graph, meaning the lockfile is stale

> ⚠️ **Security:** this workflow refuses to run when called from `pull_request_target`. That event has write access to repository secrets while checking out fork code, making it unsafe to execute Bazel on untrusted input. Use `pull_request` or `schedule` instead.

> 💡 **Recommendation:** run these checks locally via [pre-commit](https://pre-commit.com/) so issues are caught before they reach CI:
>
> ```yaml
> - repo: local
> hooks:
> - id: bzlmod-tidy
> name: bazel mod tidy
> entry: bazel mod tidy
> language: system
> pass_filenames: false
> - id: bzlmod-lockfile
> name: bazel mod deps lockfile check
> entry: bazel mod deps --lockfile_mode=error
> language: system
> pass_filenames: false
> ```

**Usage Example**

Expand All @@ -528,16 +550,17 @@ jobs:
bzlmod-lock:
uses: eclipse-score/cicd-workflows/.github/workflows/bzlmod-lock-check.yml@main
with:
working-directory: .
working-directory: . # optional, this is the default
```

**Defaults**
- `working-directory`: `.`
- `working-directory`: `.`

This workflow:
✅ Fails if `MODULE.bazel.lock` is missing
✅ Runs `bazel mod tidy`
✅ Fails if `MODULE.bazel` or `MODULE.bazel.lock` changes after tidy
✅ Fails if `bazel mod tidy` would change `MODULE.bazel` or `MODULE.bazel.lock`
✅ Fails if `bazel mod deps --lockfile_mode=error` reports a stale or inconsistent lockfile
✅ Reports both failures independently in the PR checks UI

---

Expand Down
Loading