@@ -233,3 +233,107 @@ jobs:
233233 deleted=$((deleted + 1))
234234 done < <(grep -rlE --include='ivydata-*.properties' 'exists=false' "$dir" 2>/dev/null)
235235 echo "Scrubbed $deleted poisoned ivydata marker(s) before cache save."
236+
237+ # Canary job: run the test suite against an upcoming, not-yet-released JDK.
238+ # setup-java has no first-class EA distribution, so we download the OpenJDK
239+ # EA archive ourselves and install it via `distribution: 'jdkfile'` (which
240+ # extracts/caches it and sets JAVA_HOME). We capture that JAVA_HOME as the
241+ # *test target*, then set up a stable LTS JDK afterwards so Gradle itself
242+ # runs on a GA build. `fail-fast: false` keeps EA breakage from failing the
243+ # rest of the matrix. NOTE: the EA build number bumps ~weekly, so the URL
244+ # below goes stale — refresh it (and java-version) from https://jdk.java.net/27/.
245+ ea :
246+ if : github.event_name == 'push'
247+ strategy :
248+ fail-fast : false
249+ matrix :
250+ include :
251+ - jdk-url : " https://download.java.net/java/early_access/jdk27/27/GPL/openjdk-27-ea+27_linux-x64_bin.tar.gz"
252+ jdk-version : " 27.0.0-ea.27"
253+ runs-on : ubuntu-latest
254+ steps :
255+ - name : " 📥 Checkout repository"
256+ uses : actions/checkout@v6.0.3
257+ - name : " ⬇️ Download EA JDK"
258+ run : wget -nv -O "$RUNNER_TEMP/java_package.tar.gz" "${{ matrix.jdk-url }}"
259+ - name : " ☕️ Setup EA JDK (test target)"
260+ uses : actions/setup-java@v5.2.0
261+ with :
262+ distribution : ' jdkfile'
263+ jdkFile : ${{ runner.temp }}/java_package.tar.gz
264+ java-version : ${{ matrix.jdk-version }}
265+ - name : " 📌 Capture EA JAVA_HOME"
266+ shell : bash
267+ run : echo "EA_JAVA_HOME=$JAVA_HOME" >> "$GITHUB_ENV"
268+ - name : " ☕️ Setup build JDK"
269+ # Last setup-java wins, so Gradle runs on this stable LTS JDK while the
270+ # tests target the EA JDK captured above via -Ptarget.java.home.
271+ uses : actions/setup-java@v5.2.0
272+ with :
273+ distribution : ' zulu'
274+ java-version : ' 21'
275+ check-latest : true
276+ # See the lts job for rationale; same prefix lets all jobs share the cache.
277+ - name : " 🍇 Cache @Grab artifacts (~/.groovy/grapes + ~/.m2/repository)"
278+ uses : actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
279+ with :
280+ path : |
281+ ~/.groovy/grapes
282+ ~/.m2/repository
283+ key : ${{ runner.os }}-grape-${{ github.run_id }}
284+ restore-keys : |
285+ ${{ runner.os }}-grape-
286+ - uses : gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
287+ # 'basic' uses the MIT-licensed, open-source cache provider; the default
288+ # 'enhanced' provider (v6+) is proprietary (Gradle commercial Terms of Use)
289+ with :
290+ cache-provider : basic
291+ - name : " 🌡 Pre-warm @Grab artifacts via Maven"
292+ shell : bash
293+ run : |
294+ set +e
295+ coords=$(grep -rhEo "@Grab\(\s*(value\s*=\s*)?['\"][^'\"]+['\"]" src/test subprojects/*/src/test 2>/dev/null \
296+ | sed -E "s/.*@Grab\(\s*(value\s*=\s*)?['\"]([^'\"]+)['\"].*/\2/" \
297+ | grep -E '^[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+:[a-zA-Z0-9._+-]+$' \
298+ | sort -u)
299+ [ -z "$coords" ] && { echo "No @Grab coords — skipping"; exit 0; }
300+ n=$(printf '%s\n' "$coords" | wc -l | tr -d ' ')
301+ echo "Pre-warming $n coords"
302+ ok=0; fail=0
303+ while IFS= read -r c; do
304+ if mvn -B -q dependency:get -Dartifact="$c" -Dtransitive=true >/dev/null 2>&1; then
305+ ok=$((ok+1))
306+ else
307+ fail=$((fail+1)); echo " ⚠ $c"
308+ fi
309+ done <<< "$coords"
310+ echo "Pre-warm: $ok ok / $fail failed"
311+ exit 0
312+ timeout-minutes : 15
313+ - name : " 🏃Test with Gradle"
314+ run : ./gradlew test -Pgroovy.grape.bridge-cache=true -Ptarget.java.home="$EA_JAVA_HOME"
315+ shell : bash
316+ timeout-minutes : 60
317+ # See the lts job for rationale: drop only Ivy's transient `exists=false`
318+ # negative markers just before actions/cache saves, so a one-off resolution
319+ # miss can't be cached and replayed as a sticky failure on later runs.
320+ - name : " 🧹 Scrub poisoned Grape negative-cache markers (exists=false) before save"
321+ if : always()
322+ shell : bash
323+ run : |
324+ dir=~/.groovy/grapes
325+ [ -d "$dir" ] || { echo "No $dir — nothing to scrub."; exit 0; }
326+ deleted=0
327+ while IFS= read -r f; do
328+ echo "poisoned marker: $f"
329+ grep -E 'exists=false' "$f" | sed 's/^/ /'
330+ rm -f "$f"
331+ deleted=$((deleted + 1))
332+ done < <(grep -rlE --include='ivydata-*.properties' 'exists=false' "$dir" 2>/dev/null)
333+ echo "Scrubbed $deleted poisoned ivydata marker(s) before cache save."
334+ - name : " 🚀Upload reports"
335+ uses : actions/upload-artifact@v7
336+ if : always()
337+ with :
338+ name : build-reports-ea
339+ path : ' **/build/reports/'
0 commit comments