Release Project workflow (google/closure-compiler)
The Release Project workflow from google/closure-compiler, explained and optimized by Latchkey.
- D
- CI health grade
- 53/100
- Health score
- 3
- Jobs per trigger
- 4
- Fixes in the optimized version
In short
- CI health: D (needs work)
- The optimized version below points runs-on at Latchkey managed runners and adds run de-duplication, caching, and job timeouts.
What it does
This is the Release Project workflow from the google/closure-compiler repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.0rg/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Release Project
permissions:
contents: read
# This workflow is triggered by pushes to the `master` branch that modify '.github/piper-cut-cl.json'.
# When `piper-cut-cl.json` changes, indicating a new release cut, the workflow proceeds to:
# 1. Tag the repository at the current commit with the new version ID.
# 2. Run the test suite.
# 3. If tests pass, publish the release artifacts to Sonatype.
on:
push:
branches: [ master ]
paths:
- '.github/piper-cut-cl.json'
jobs:
tag_release_version_id:
name: Tag Release Version ID
runs-on: ubuntu-latest
permissions:
contents: write
outputs:
tag: ${{ fromJson(steps.set_piper_json_vars.outputs.piperJson).version_id }}
steps:
# Checkout the piper-cut-cl.json from the most recent commit
- name: Checkout Current closure-compiler Commit
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
sparse-checkout: |
.github/piper-cut-cl.json
.github/ci_support/tag_release_version.sh
sparse-checkout-cone-mode: false
- id: set_piper_json_vars
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
run: |
{
echo 'piperJson<<EOF'
cat ./.github/piper-cut-cl.json
echo EOF
} >> "$GITHUB_OUTPUT"
# Fetch all historical commits
- name: Checkout all closure-compiler commits and tags
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# a depth of 0 triggers a fetch of all historical commits/branches/tags
# https://github.com/marketplace/actions/checkout#Fetch-all-history-for-all-tags-and-branches
fetch-depth: 0
- name: Tag cut CL
id: tag_cut_cl
run: |
git config --global user.name "${GITHUB_ACTOR}"
git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
.github/ci_support/tag_release_version.sh \
"${{ fromJson(env.piperJson).piper_cut_cl }}" \
"${{ fromJson(env.piperJson).version_id }}";
env:
piperJson: ${{ steps.set_piper_json_vars.outputs.piperJson }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run-tests:
uses: ./.github/workflows/run_tests.yaml
needs: tag_release_version_id
with:
ref: ${{ needs.tag_release_version_id.outputs.tag }}
publish-to-sonatype:
runs-on: ubuntu-latest
# Only publish if tests passed.
needs: [tag_release_version_id, run-tests]
steps:
- name: Check secrets are provided
run: |
check_secret() {
if [[ -z "${2:-}" ]]; then
echo "Error: secret ${1} is required but not provided."
exit 1
fi
}
check_secret "SONATYPE_USERNAME_TOKEN" "${{ secrets.SONATYPE_USERNAME_TOKEN }}"
check_secret "SONATYPE_PASSWORD_TOKEN" "${{ secrets.SONATYPE_PASSWORD_TOKEN }}"
check_secret "MAVEN_GPG_PRIVATE_KEY" "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}"
check_secret "MAVEN_GPG_PASSPHRASE" "${{ secrets.MAVEN_GPG_PASSPHRASE }}"
- name: Checkout the repo at the release tag
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.tag_release_version_id.outputs.tag }}
persist-credentials: false
# Maven and Bazel require Java to be available.
- name: Setup Java
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with:
java-version: '21'
distribution: 'zulu'
java-package: jdk
# This will trigger the import of the private key in the GPG secret keyring.
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
# This will create the maven settings.xml file needed for deploying to Sonatype.
server-id: central
# Configure Maven settings to use environment variables. These environment variables are
# populated from GitHub secrets in the publication steps.
server-username: SONATYPE_USERNAME_TOKEN
server-password: SONATYPE_PASSWORD_TOKEN
gpg-passphrase: MAVEN_GPG_PASSPHRASE
# https://github.com/bazel-contrib/setup-bazel
- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
# Avoid downloading Bazel every time.
bazelisk-cache: true
# Store build cache per workflow.
disk-cache: ${{ github.workflow }}
# Share repository cache between workflows
repository-cache: true
- name: Upload and Publish to Sonatype
run: |
./release.sh --sonatype-auto-publish
env:
RELEASE_NUM: ${{ needs.tag_release_version_id.outputs.tag }}
SONATYPE_USERNAME_TOKEN: ${{ secrets.SONATYPE_USERNAME_TOKEN }}
SONATYPE_PASSWORD_TOKEN: ${{ secrets.SONATYPE_PASSWORD_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.0rg/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: Release Project
permissions:
contents: read
# This workflow is triggered by pushes to the `master` branch that modify '.github/piper-cut-cl.json'.
# When `piper-cut-cl.json` changes, indicating a new release cut, the workflow proceeds to:
# 1. Tag the repository at the current commit with the new version ID.
# 2. Run the test suite.
# 3. If tests pass, publish the release artifacts to Sonatype.
on:
push:
branches: [ master ]
paths:
- '.github/piper-cut-cl.json'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tag_release_version_id:
timeout-minutes: 30
name: Tag Release Version ID
runs-on: ubuntu-latest
runs-on: latchkey-small
permissions:
contents: write
outputs:
tag: ${{ fromJson(steps.set_piper_json_vars.outputs.piperJson).version_id }}
steps:
# Checkout the piper-cut-cl.json from the most recent commit
- name: Checkout Current closure-compiler Commit
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
sparse-checkout: |
.github/piper-cut-cl.json
.github/ci_support/tag_release_version.sh
sparse-checkout-cone-mode: false
- id: set_piper_json_vars
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
run: |
{
echo 'piperJson<<EOF'
cat ./.github/piper-cut-cl.json
echo EOF
} >> "$GITHUB_OUTPUT"
# Fetch all historical commits
- name: Checkout all closure-compiler commits and tags
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# a depth of 0 triggers a fetch of all historical commits/branches/tags
# https://github.com/marketplace/actions/checkout#Fetch-all-history-for-all-tags-and-branches
fetch-depth: 0
- name: Tag cut CL
id: tag_cut_cl
run: |
git config --global user.name "${GITHUB_ACTOR}"
git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com"
.github/ci_support/tag_release_version.sh \
"${{ fromJson(env.piperJson).piper_cut_cl }}" \
"${{ fromJson(env.piperJson).version_id }}";
env:
piperJson: ${{ steps.set_piper_json_vars.outputs.piperJson }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run-tests:
timeout-minutes: 30
uses: ./.github/workflows/run_tests.yaml
needs: tag_release_version_id
with:
ref: ${{ needs.tag_release_version_id.outputs.tag }}
publish-to-sonatype:
runs-on: ubuntu-latest
timeout-minutes: 30
runs-on: latchkey-small
# Only publish if tests passed.
needs: [tag_release_version_id, run-tests]
steps:
- name: Check secrets are provided
run: |
check_secret() {
if [[ -z "${2:-}" ]]; then
echo "Error: secret ${1} is required but not provided."
exit 1
fi
}
check_secret "SONATYPE_USERNAME_TOKEN" "${{ secrets.SONATYPE_USERNAME_TOKEN }}"
check_secret "SONATYPE_PASSWORD_TOKEN" "${{ secrets.SONATYPE_PASSWORD_TOKEN }}"
check_secret "MAVEN_GPG_PRIVATE_KEY" "${{ secrets.MAVEN_GPG_PRIVATE_KEY }}"
check_secret "MAVEN_GPG_PASSPHRASE" "${{ secrets.MAVEN_GPG_PASSPHRASE }}"
- name: Checkout the repo at the release tag
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ needs.tag_release_version_id.outputs.tag }}
persist-credentials: false
# Maven and Bazel require Java to be available.
- name: Setup Java
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with:
cache: 'maven'
java-version: '21'
distribution: 'zulu'
java-package: jdk
# This will trigger the import of the private key in the GPG secret keyring.
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
# This will create the maven settings.xml file needed for deploying to Sonatype.
server-id: central
# Configure Maven settings to use environment variables. These environment variables are
# populated from GitHub secrets in the publication steps.
server-username: SONATYPE_USERNAME_TOKEN
server-password: SONATYPE_PASSWORD_TOKEN
gpg-passphrase: MAVEN_GPG_PASSPHRASE
# https://github.com/bazel-contrib/setup-bazel
- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
# Avoid downloading Bazel every time.
bazelisk-cache: true
# Store build cache per workflow.
disk-cache: ${{ github.workflow }}
# Share repository cache between workflows
repository-cache: true
- name: Upload and Publish to Sonatype
run: |
./release.sh --sonatype-auto-publish
env:
RELEASE_NUM: ${{ needs.tag_release_version_id.outputs.tag }}
SONATYPE_USERNAME_TOKEN: ${{ secrets.SONATYPE_USERNAME_TOKEN }}
SONATYPE_PASSWORD_TOKEN: ${{ secrets.SONATYPE_PASSWORD_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. Each Linux x64 job moves to the Latchkey size matching its vCPU count,latchkey-smallfor a standard 2 vCPU runner; macOS, Windows and arm64 jobs stay as written. - Cancel superseded runs when a branch or PR gets a newer push.
- Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
This workflow runs 3 jobs per trigger. 2 of the 3 run on a standard GitHub-hosted Linux x64 runner, which in a private repository bills $0.006 a minute against $0.0025 on latchkey-small, 58% lower; in a public repository like this one they are free. Estimate the monthly difference with your own run counts.
Actions used in this workflow
- actions/checkout
- actions/setup-java
- bazel-contrib/setup-bazel