Skip to content
Latchkey

Release Project workflow (google/closure-compiler)

The Release Project workflow from google/closure-compiler, explained and optimized by Latchkey.

D

CI health: D - needs work

Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: google/closure-compiler.github/workflows/release.yamlLicense Apache-2.0View source

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

workflow (.yml)
# 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: 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:
    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

This workflow runs 3 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow