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"

Reply via email to