diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02ef91c6..75ad96d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,14 +51,31 @@ jobs: set -euo pipefail test ! -e .gradle test ! -e "$GRADLE_USER_HOME" - mkdir -p "$GRADLE_USER_HOME" chmod +x ./gradlew - ./gradlew verifyMavenizerCompatibilityFixture help \ - --no-daemon --no-build-cache --stacktrace --max-workers=2 \ - -Dorg.gradle.java.installations.paths="$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" \ - -Dorg.gradle.java.installations.auto-detect=false \ - -Dorg.gradle.java.installations.auto-download=false \ - | tee "$RUNNER_TEMP/cold-forge-bootstrap.log" + gradle_args=( + verifyMavenizerCompatibilityFixture help + --no-daemon --no-build-cache --stacktrace --max-workers=2 + "-Dorg.gradle.java.installations.paths=$JAVA_HOME,$JAVA_HOME_8_X64,$JAVA_HOME_25_X64" + -Dorg.gradle.java.installations.auto-detect=false + -Dorg.gradle.java.installations.auto-download=false + ) + for attempt in 1 2 3; do + if (( attempt > 1 )); then + echo "Retrying the cold Forge bootstrap after transient download failure ($attempt/3)" + rm -rf -- "$GRADLE_USER_HOME/caches/minecraftforge" .gradle/mavenizer + sleep "$((attempt * 5))" + fi + mkdir -p "$GRADLE_USER_HOME" + attempt_log="$RUNNER_TEMP/cold-forge-bootstrap-attempt-$attempt.log" + if ./gradlew "${gradle_args[@]}" 2>&1 | tee "$attempt_log"; then + cp "$attempt_log" "$RUNNER_TEMP/cold-forge-bootstrap.log" + break + fi + if (( attempt == 3 )); then + echo "Cold Forge bootstrap failed after $attempt attempts" >&2 + exit 1 + fi + done grep -F "OreSpawn Mavenizer compatibility runtime: Java 25.0.3" \ "$RUNNER_TEMP/cold-forge-bootstrap.log" grep -F "OreSpawn Mavenizer compatibility: applied 0 rule(s) for net.minecraftforge:forge:1.21.1-52.1.0 (explicit no-op)" \ diff --git a/src/test/java/zone/moddev/mc/orespawn/ReleaseWorkflowContractTest.java b/src/test/java/zone/moddev/mc/orespawn/ReleaseWorkflowContractTest.java index f0c480e5..f4b56472 100644 --- a/src/test/java/zone/moddev/mc/orespawn/ReleaseWorkflowContractTest.java +++ b/src/test/java/zone/moddev/mc/orespawn/ReleaseWorkflowContractTest.java @@ -85,6 +85,20 @@ void codeQlUsesABoundedCachePreservingCompileRetry() throws Exception { "CodeQL must fail rather than hide a persistent bootstrap defect"); } + @Test + void coldForgeBootstrapRetriesTransientDownloadsWithoutAcceptingPersistentFailure() throws Exception { + String text = new String(Files.readAllBytes( + Paths.get(".github", "workflows", "ci.yml")), StandardCharsets.UTF_8); + assertTrue(text.contains("for attempt in 1 2 3; do"), + "Cold Forge bootstrap must bound transient download retries"); + assertTrue(text.contains("./gradlew \"${gradle_args[@]}\" 2>&1 | tee \"$attempt_log\""), + "Every retry must preserve the exact Gradle bootstrap arguments and diagnostics"); + assertTrue(text.contains("rm -rf -- \"$GRADLE_USER_HOME/caches/minecraftforge\" .gradle/mavenizer"), + "Retries must discard partial Forge and Mavenizer state"); + assertTrue(text.contains("Cold Forge bootstrap failed after $attempt attempts"), + "A persistent bootstrap failure must still fail CI"); + } + private static int occurrences(String text, String needle) { int count = 0; int offset = 0;