This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new dea71cc8df github actions for grape caching
dea71cc8df is described below
commit dea71cc8df77b466dd601a00158f8e9565e9e6cd
Author: Paul King <[email protected]>
AuthorDate: Sun May 10 15:15:44 2026 +1000
github actions for grape caching
---
.github/workflows/groovy-build-coverage.yml | 16 +++
.github/workflows/groovy-build-test.yml | 27 ++++
.github/workflows/groovy-purge-grape-cache.yml | 166 +++++++++++++++++++++++++
3 files changed, 209 insertions(+)
diff --git a/.github/workflows/groovy-build-coverage.yml
b/.github/workflows/groovy-build-coverage.yml
index a9f6e257b1..e2a8b43e04 100644
--- a/.github/workflows/groovy-build-coverage.yml
+++ b/.github/workflows/groovy-build-coverage.yml
@@ -37,6 +37,22 @@ jobs:
distribution: 'zulu'
java-version: 21
check-latest: true
+ # `setup-gradle` caches ~/.gradle/caches but NOT ~/.groovy/grapes,
+ # which is where @Grab-resolved artifacts (used by tests like
+ # GenericsSTCTest, MethodReferenceTest, β¦) land. Caching it makes
+ # the build resilient to transient Maven Central / CDN outages:
+ # once an artifact has been resolved on any prior run, subsequent
+ # runs reuse it from cache and don't re-hit the network.
+ #
+ # Same key prefix as `groovy-build-test.yml` so the two workflows
+ # share their accumulated Grape cache.
+ - name: "π Cache @Grab artifacts (~/.groovy/grapes)"
+ uses: actions/cache@v4
+ with:
+ path: ~/.groovy/grapes
+ key: ${{ runner.os }}-grape-${{ github.run_id }}
+ restore-keys: |
+ ${{ runner.os }}-grape-
- uses:
gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
- name: Test with Gradle
run: ./gradlew -Pcoverage=true jacocoAllReport
diff --git a/.github/workflows/groovy-build-test.yml
b/.github/workflows/groovy-build-test.yml
index 06b695822f..1ef8b6fd57 100644
--- a/.github/workflows/groovy-build-test.yml
+++ b/.github/workflows/groovy-build-test.yml
@@ -53,6 +53,24 @@ jobs:
${{ matrix.java }}
21
check-latest: true
+ # `setup-gradle` below caches ~/.gradle/caches but NOT ~/.groovy/grapes,
+ # which is where @Grab-resolved artifacts (used by tests like
+ # GenericsSTCTest, MethodReferenceTest, β¦) land. Caching it makes the
+ # build resilient to transient Maven Central / CDN outages: once an
+ # artifact has been resolved on any prior run, subsequent runs reuse
+ # it from cache and don't re-hit the network.
+ #
+ # Key strategy: per-run key + prefix restore-keys. Each run saves a
+ # fresh entry; the next run finds the most recent via prefix
+ # fallback. The cache grows with new @Grab coordinates over time and
+ # never gets invalidated by older ones being removed.
+ - name: "π Cache @Grab artifacts (~/.groovy/grapes)"
+ uses: actions/cache@v4
+ with:
+ path: ~/.groovy/grapes
+ key: ${{ runner.os }}-grape-${{ github.run_id }}
+ restore-keys: |
+ ${{ runner.os }}-grape-
- name: "π Setup Gradle"
uses:
gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
- name: "π Setup TestLens"
@@ -85,6 +103,15 @@ jobs:
${{ matrix.java }}
21
check-latest: true
+ # See the lts job for rationale; same prefix lets both jobs share
+ # the cache.
+ - name: "π Cache @Grab artifacts (~/.groovy/grapes)"
+ uses: actions/cache@v4
+ with:
+ path: ~/.groovy/grapes
+ key: ${{ runner.os }}-grape-${{ github.run_id }}
+ restore-keys: |
+ ${{ runner.os }}-grape-
- uses:
gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
- name: "πTest with Gradle"
run: ./gradlew test -Ptarget.java.home="$JAVA_HOME_${{ matrix.java
}}_X64"
diff --git a/.github/workflows/groovy-purge-grape-cache.yml
b/.github/workflows/groovy-purge-grape-cache.yml
new file mode 100644
index 0000000000..75f64f02f1
--- /dev/null
+++ b/.github/workflows/groovy-purge-grape-cache.yml
@@ -0,0 +1,166 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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
+#
+# http://www.apache.org/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.
+
+# Manually services the `actions/cache` entries that hold the persisted
+# `~/.groovy/grapes/` directory used by the build/test workflows.
+#
+# Two modes:
+#
+# * `delete-entries` β wholesale removal of cache entries via
+# `gh cache delete`. Loses all cached JARs; the next regular workflow
+# run will repopulate from fresh resolutions. Use when a JAR itself
+# is corrupt or you want to start completely clean.
+#
+# * `scrub-ivydata` β restore the latest cache, delete only the
+# `ivydata-*.properties` files (Ivy's per-artifact resolver-state
+# cache, where transient "not found" responses get memoised), and
+# save back under a fresh key. Preserves all the actual JARs and
+# POMs. Use when CI is failing with `unresolved dependency` against
+# artifacts that *do* exist on Maven Central β the classic poisoned-
+# negative-cache symptom.
+#
+# After either mode, the next run of `Build and test` / `Build and test
+# for coverage` will pick up the cleaned (or absent) cache via the
+# `${{ runner.os }}-grape-` restore-keys prefix.
+
+name: Purge Grape cache
+
+on:
+ workflow_dispatch:
+ inputs:
+ mode:
+ description: 'What to do'
+ required: true
+ type: choice
+ default: scrub-ivydata
+ options:
+ - scrub-ivydata
+ - delete-entries
+ key_prefix:
+ description: 'Cache key substring to match (delete-entries mode only;
scrub-ivydata always operates on the latest cache per OS)'
+ required: false
+ default: 'grape-'
+ dry_run:
+ description: 'List/preview without modifying'
+ required: false
+ type: boolean
+ default: false
+
+permissions:
+ # `actions: write` is required both for `gh cache delete` and for the
+ # `actions/cache` post-step that saves a fresh entry in scrub mode.
+ actions: write
+ contents: read
+
+jobs:
+ delete-entries:
+ if: ${{ inputs.mode == 'delete-entries' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: "π List matching cache entries"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ PREFIX: ${{ inputs.key_prefix }}
+ run: |
+ echo "Caches whose keys contain '${PREFIX}':"
+ gh cache list --limit 100 \
+ --json id,key,sizeInBytes,createdAt \
+ --jq ".[] | select(.key | contains(\"${PREFIX}\"))" \
+ | tee matches.json
+ echo
+ echo "Total matched: $(jq -s 'length' matches.json)"
+ echo "Total size: $(jq -s 'map(.sizeInBytes) | add // 0'
matches.json) bytes"
+ - name: "ποΈ Delete matching cache entries"
+ if: ${{ inputs.dry_run == false }}
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ PREFIX: ${{ inputs.key_prefix }}
+ run: |
+ # `gh cache list` paginates by default; --limit 100 plus the
+ # loop below handles repos with many entries (purge tends to
+ # be one-shot anyway).
+ while true; do
+ ids=$(gh cache list --limit 100 \
+ --json id,key \
+ --jq ".[] | select(.key | contains(\"${PREFIX}\")) | .id")
+ if [ -z "$ids" ]; then
+ echo "No more matching caches."
+ break
+ fi
+ echo "$ids" | while read -r id; do
+ [ -z "$id" ] && continue
+ echo "Deleting cache id=$id"
+ gh cache delete "$id"
+ done
+ done
+
+ # Surgical scrub: per-OS, restore the most recent grape cache for that
+ # OS, delete only the `ivydata-*.properties` negative-cache markers,
+ # then let actions/cache's post-step save the cleaned tree under a
+ # fresh run-id-keyed entry. All JARs/POMs survive.
+ #
+ # The matrix mirrors the OSes that produce grape caches in the build
+ # workflows (Linux/Windows/macOS). Each scrubs its own key family.
+ scrub-ivydata:
+ if: ${{ inputs.mode == 'scrub-ivydata' }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: "π Restore latest grape cache for ${{ runner.os }}"
+ uses: actions/cache@v4
+ with:
+ path: ~/.groovy/grapes
+ # New entry key per run so the cleaned tree gets saved under a
+ # fresh tag (actions/cache won't overwrite an existing key).
+ # Restore-keys finds the most recent `<os>-grape-*` entry.
+ key: ${{ runner.os }}-grape-${{ github.run_id }}
+ restore-keys: |
+ ${{ runner.os }}-grape-
+ - name: "π Inventory poisoned ivydata-*.properties markers"
+ shell: bash
+ run: |
+ if [ ! -d ~/.groovy/grapes ]; then
+ echo "No grape cache restored on ${{ runner.os }} β nothing to do."
+ exit 0
+ fi
+ echo "ivydata-*.properties files in ~/.groovy/grapes:"
+ # Print each file with size and mtime, plus a count.
+ # `find ... -printf` isn't portable to macOS, so use stat.
+ count=0
+ while IFS= read -r f; do
+ count=$((count + 1))
+ ls -l "$f"
+ done < <(find ~/.groovy/grapes -name 'ivydata-*.properties'
2>/dev/null)
+ echo
+ echo "Total markers: $count"
+ - name: "π§Ή Scrub ivydata-*.properties markers"
+ if: ${{ inputs.dry_run == false }}
+ shell: bash
+ run: |
+ if [ ! -d ~/.groovy/grapes ]; then
+ echo "No grape cache restored β skipping."
+ exit 0
+ fi
+ deleted=$(find ~/.groovy/grapes -name 'ivydata-*.properties' -print
-delete | wc -l)
+ echo "Deleted $deleted ivydata-*.properties marker(s)."
+ # Sanity-check that JARs survived. A non-zero count here is
+ # the whole point of scrub mode vs. delete-entries mode.
+ jars=$(find ~/.groovy/grapes -name '*.jar' 2>/dev/null | wc -l)
+ echo "JARs remaining in cache: $jars"