diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28cabbd..68e965c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,19 @@ -name: Sky's Building Pieces - Biomes O Plenty 1.10 CI +name: Sky's Building Pieces - Biomes O Plenty CI on: push: branches: - - master-1.10.2 + - 'master-*' - 'feature/**' pull_request: branches: - - master-1.10.2 + - 'master-*' permissions: contents: read concurrency: - group: skysbuildingpieces-biomesoplenty-addon-1.10-${{ github.workflow }}-${{ github.ref }} + group: skysbuildingpieces-biomesoplenty-addon-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: @@ -26,27 +26,43 @@ jobs: - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Install Java 8 toolchain + - name: Read target build metadata + id: target + shell: bash + run: | + set -euo pipefail + value() { sed -n "s/^$1=//p" gradle.properties; } + toolchain="$(value java_toolchain_version)" + selector="$(value java_setup_version)" + echo "java_setup_version=${selector:-$toolchain}" >> "$GITHUB_OUTPUT" + echo "java_version=$(value java_version)" >> "$GITHUB_OUTPUT" + echo "minecraft_version=$(value minecraft_version)" >> "$GITHUB_OUTPUT" + echo "gradle_java_version=$(value gradle_java_version)" >> "$GITHUB_OUTPUT" + echo "artifact_prefix=SkysBuildingPieces-BiomesOPlenty-Addon-$(value mod_version)" >> "$GITHUB_OUTPUT" + + - name: Install target Java toolchain uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin - java-version: '8.0.502+7' + java-version: ${{ steps.target.outputs.java_setup_version }} - name: Pin production Java + if: steps.target.outputs.java_version == '8' run: | echo "SGS_JAVA8_HOME=$JAVA_HOME" >> "$GITHUB_ENV" echo "SBP_JAVA8_HOME=$JAVA_HOME" >> "$GITHUB_ENV" echo "STS_JAVA8_HOME=$JAVA_HOME" >> "$GITHUB_ENV" - - name: Install Java 17 for Gradle + - name: Install Java for Gradle uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: microsoft - java-version: '17' + java-version: ${{ steps.target.outputs.gradle_java_version }} - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + - name: Read pinned dependency sources id: content run: | @@ -66,6 +82,7 @@ jobs: chmod +x dependencies/*/gradlew (cd dependencies/pieces && ./gradlew jar --no-daemon --stacktrace) + - name: Make the wrapper executable run: chmod +x ./gradlew @@ -76,19 +93,18 @@ jobs: - name: Replay artifact checks offline run: >- - ./gradlew assemble verifyReleaseArtifacts writeReleaseChecksums - -PbuildingPiecesCoreDir=dependencies/pieces --offline --no-daemon --stacktrace + ./gradlew assemble verifyReleaseArtifacts writeReleaseChecksums -PbuildingPiecesCoreDir=dependencies/pieces --offline --no-daemon --stacktrace - name: Upload audited release candidate uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: SkysBuildingPieces-BiomesOPlenty-Addon-1.10.2-${{ github.sha }} + name: SkysBuildingPieces-BiomesOPlenty-Addon-${{ steps.target.outputs.minecraft_version }}-${{ github.sha }} if-no-files-found: error retention-days: 30 path: | - build/libs/SkysBuildingPieces-BiomesOPlenty-Addon-0.1.2.110021.jar - build/libs/SkysBuildingPieces-BiomesOPlenty-Addon-0.1.2.110021-sources.jar - build/libs/SkysBuildingPieces-BiomesOPlenty-Addon-0.1.2.110021-javadoc.jar + build/libs/${{ steps.target.outputs.artifact_prefix }}.jar + build/libs/${{ steps.target.outputs.artifact_prefix }}-sources.jar + build/libs/${{ steps.target.outputs.artifact_prefix }}-javadoc.jar build/release/SHA256SUMS CHANGELOG.md @@ -96,7 +112,7 @@ jobs: if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: SkysBuildingPieces-BiomesOPlenty-Addon-1.10.2-diagnostics-${{ github.sha }} + name: SkysBuildingPieces-BiomesOPlenty-Addon-${{ steps.target.outputs.minecraft_version }}-diagnostics-${{ github.sha }} if-no-files-found: ignore retention-days: 14 path: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 348fd73..64c5606 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -3,11 +3,11 @@ name: CodeQL on: push: branches: - - master-1.10.2 + - 'master-*' - 'feature/**' pull_request: branches: - - master-1.10.2 + - 'master-*' schedule: - cron: '47 7 * * 4' @@ -26,27 +26,43 @@ jobs: - name: Check out source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - name: Install Java 8 toolchain + - name: Read target build metadata + id: target + shell: bash + run: | + set -euo pipefail + value() { sed -n "s/^$1=//p" gradle.properties; } + toolchain="$(value java_toolchain_version)" + selector="$(value java_setup_version)" + echo "java_setup_version=${selector:-$toolchain}" >> "$GITHUB_OUTPUT" + echo "java_version=$(value java_version)" >> "$GITHUB_OUTPUT" + echo "minecraft_version=$(value minecraft_version)" >> "$GITHUB_OUTPUT" + echo "gradle_java_version=$(value gradle_java_version)" >> "$GITHUB_OUTPUT" + echo "artifact_prefix=SkysBuildingPieces-BiomesOPlenty-Addon-$(value mod_version)" >> "$GITHUB_OUTPUT" + + - name: Install target Java toolchain uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: temurin - java-version: '8.0.502+7' + java-version: ${{ steps.target.outputs.java_setup_version }} - name: Pin production Java + if: steps.target.outputs.java_version == '8' run: | echo "SGS_JAVA8_HOME=$JAVA_HOME" >> "$GITHUB_ENV" echo "SBP_JAVA8_HOME=$JAVA_HOME" >> "$GITHUB_ENV" echo "STS_JAVA8_HOME=$JAVA_HOME" >> "$GITHUB_ENV" - - name: Install Java 17 for Gradle + - name: Install Java for Gradle uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: microsoft - java-version: '17' + java-version: ${{ steps.target.outputs.gradle_java_version }} - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 + - name: Read pinned dependency sources id: content run: | @@ -66,6 +82,7 @@ jobs: chmod +x dependencies/*/gradlew (cd dependencies/pieces && ./gradlew jar --no-daemon --stacktrace) + - name: Initialize CodeQL uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4 with: diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index 6c941ea..e86a283 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -324,8 +324,8 @@ jobs: if [[ "$LOADER_NAME" == "neoforge" ]]; then # NeoGradle resolves model inputs during configuration under build/tmp. # Cleaning in the same invocation removes those inputs before setup runs. - ./gradlew clean --no-daemon --stacktrace --max-workers=2 - ./gradlew check build javadoc verifyReleaseArtifacts writeReleaseChecksums \ + ./gradlew clean -PbuildingPiecesCoreDir=dependencies/pieces --no-daemon --stacktrace --max-workers=2 + ./gradlew check build javadoc verifyReleaseArtifacts writeReleaseChecksums -PbuildingPiecesCoreDir=dependencies/pieces \ --no-daemon --stacktrace --max-workers=2 else # Preserve the established Forge release lifecycle. @@ -480,6 +480,13 @@ jobs: distribution: temurin java-version: ${{ needs.preflight.outputs.gradle_java_version }} + - name: Pin Java 8 dependency builds + if: needs.preflight.outputs.java_version == '8' + run: | + echo "SGS_JAVA8_HOME=$JAVA_HOME_8_X64" >> "$GITHUB_ENV" + echo "SBP_JAVA8_HOME=$JAVA_HOME_8_X64" >> "$GITHUB_ENV" + echo "STS_JAVA8_HOME=$JAVA_HOME_8_X64" >> "$GITHUB_ENV" + - name: Set up Gradle uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 @@ -564,10 +571,14 @@ jobs: fi - name: Publish Sky's Building Pieces - Biomes O Plenty + id: curseforge uses: Kira-NT/mc-publish@52307b03863581dec6b652b83e597aec02ebb075 # v3.3.1 with: curseforge-token: ${{ secrets.CURSEFORGE_TOKEN }} curseforge-id: ${{ needs.preflight.outputs.curseforge_project_id }} + curseforge-dependencies: | + 1754162(required) + 220318(required) name: Sky's Building Pieces - Biomes O Plenty ${{ needs.preflight.outputs.release_version }} for Minecraft ${{ needs.preflight.outputs.minecraft_version }} (${{ needs.preflight.outputs.loader_display }}) version: ${{ needs.preflight.outputs.release_version }} version-type: ${{ inputs.curseforge_release_level }} @@ -583,6 +594,16 @@ jobs: ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.sources_jar }} ${{ runner.temp }}/release-input/${{ needs.preflight.outputs.javadoc_jar }} + - name: Record CurseForge publication + env: + CURSEFORGE_FILE_ID: ${{ steps.curseforge.outputs.curseforge-version }} + CURSEFORGE_URL: ${{ steps.curseforge.outputs.curseforge-url }} + run: | + set -euo pipefail + [[ "$CURSEFORGE_FILE_ID" =~ ^[0-9]+$ ]] + printf 'CurseForge file: %s\nDownload: %s\n' "$CURSEFORGE_FILE_ID" "$CURSEFORGE_URL" + printf '### CurseForge publication\n\nFile: %s\n\nDownload: %s\n' "$CURSEFORGE_FILE_ID" "$CURSEFORGE_URL" >> "$GITHUB_STEP_SUMMARY" + publish_github: name: Publish GitHub Release needs: diff --git a/.github/workflows/release-on-tag.yml b/.github/workflows/release-on-tag.yml index 6009228..82501ed 100644 --- a/.github/workflows/release-on-tag.yml +++ b/.github/workflows/release-on-tag.yml @@ -19,7 +19,11 @@ jobs: set -euo pipefail version="$(sed -n 's/^mod_version=//p' gradle.properties)" test "$GITHUB_REF_NAME" = "$version" - [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.110021$ ]] + minecraft="$(sed -n 's/^minecraft_version=//p' gradle.properties)" + loader="$(sed -n 's/^loader_code=//p' gradle.properties)" + IFS=. read -r major minor patch <<<"$minecraft" + printf -v suffix '%s%02d%02d%s' "$major" "$((10#$minor))" "$((10#${patch:-0}))" "$loader" + [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.${suffix}$ ]] checks="$(gh api "repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/check-runs?per_page=100" --jq '[.check_runs[] | select(.name == "Build, test, and audit" and .conclusion == "success")] | length')" test "$checks" -gt 0 echo "verifyReleaseArtifacts passed in CI. This tag check publishes nothing." diff --git a/.github/workflows/validate-gradle-build.yml b/.github/workflows/validate-gradle-build.yml index 81f7cd4..d71fd0e 100644 --- a/.github/workflows/validate-gradle-build.yml +++ b/.github/workflows/validate-gradle-build.yml @@ -3,11 +3,11 @@ name: Validate Gradle Wrapper on: push: branches: - - master-1.10.2 + - 'master-*' - 'feature/**' pull_request: branches: - - master-1.10.2 + - 'master-*' permissions: contents: read diff --git a/README.md b/README.md index eb5f36d..48b1483 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,9 @@ Unsupported materials, shapes or tools may still need a replacement. Unsupported absent-mod recovery stops rather than guessing at a replacement. See [crafting](docs/RECIPES.md), [building and testing](docs/BUILDING.md), [existing worlds](docs/WORLD-UPGRADES.md) -and [trying the beta](docs/TRYING-THE-BETA.md). Publication is not yet enabled. +and [trying the beta](docs/TRYING-THE-BETA.md). + +Downloads are available on [CurseForge](https://www.curseforge.com/minecraft/mc-mods/skysbuildingpiecesbop). [Release preparation](docs/RELEASING.md) covers the CI checks and publication safeguards. diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 59c05c3..eec3470 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -22,5 +22,4 @@ candidate; it requires an explicit core directory and is rejected in CI. Use `genEclipseRuns eclipse` with the same core-directory property to prepare the IDE. Ordinary client and server launches exclude unit tests and the -build-only runtime probe. Publication remains disabled until this add-on has -its own CurseForge project. +build-only runtime probe. Release preparation is covered in [the release guide](RELEASING.md). diff --git a/docs/RELEASING.md b/docs/RELEASING.md index fc47f75..b01d7d0 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -4,20 +4,28 @@ Pushes and pull requests run the build, tests, artifact audit, CodeQL and Gradle wrapper checks. CI uploads the main, sources and Javadoc jars with their checksums. Tags only validate a candidate; they never publish it. -Publication is currently disabled. Before the first release, create this mod's -CurseForge project, set its own `curseforge_project_id` in `gradle.properties`, -and set `release_enabled=true`. Do not reuse another mod's project ID. +This add-on publishes to CurseForge project 1755976. Publication is enabled only +in MMD; a fork build never uploads a release. Building Pieces and Biomes O Plenty +are marked as required dependencies. Release the matching core version first. The MMD release workflow uses the organisation's `CURSEFORGE_TOKEN`, `MAVEN_UPLOAD_URL`, `MAVEN_UPLOAD_USERNAME` and `MAVEN_UPLOAD_PASSWORD`. It requires a passing CI build and explicit confirmation of live publication. -Enter the full release version and choose the CurseForge release level. +Enter the full release version, choose `release` for the approved stable +candidate, and confirm live publication. -The workflow resolves the Minecraft branch from the version, rebuilds the exact -commit, and checks one immutable artifact bundle. It validates or creates the tag, -then publishes the same files to Maven, CurseForge and GitHub, in that order. -A failed step prevents the later publication steps. Check the public downloads -before announcing a release. +The workflow resolves the Minecraft branch from the version, builds the exact +commit and its pinned core dependency, and checks one immutable artifact bundle. +It validates or creates the tag, then publishes the same files to Maven, +CurseForge and GitHub, in that order. A failed step prevents later publication. +Check the public downloads before announcing a release. -Content mods must be released before add-ons that require them. Keep dependency -source pins up to date when preparing a coordinated release. +The dispatcher reads the selected branch's Minecraft, loader, Java and artifact +metadata. It is not restricted to 1.10 or 1.12: later Forge and NeoForge ports +use the same version suffix and branch naming rules. Each port must supply its +own tested build setup, dependency pin and correct metadata. + +If publication stops, inspect the completed stages before retrying. Keep the +same commit and immutable bundle; rerun only failed jobs when an upload has +already succeeded. Do not create another CurseForge file while one is awaiting +approval. An existing release with the same jar does not need another upload. diff --git a/gradle.properties b/gradle.properties index c54a370..3953ebf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,8 +16,8 @@ java_toolchain_version=8.0.502+7 gradle_java_version=17 mod_id=skysbuildingpiecesbop release_repository=MinecraftModDevelopmentMods/SkysBuildingPieces-BiomesOPlenty-Addon -release_enabled=false -curseforge_project_id= +release_enabled=true +curseforge_project_id=1755976 mod_name=Sky's Building Pieces - Biomes O Plenty mod_license=LGPL-2.1-only mod_version=0.1.2.110021 diff --git a/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/PublicationContractTest.java b/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/PublicationContractTest.java index 0618cd1..37cdaf8 100644 --- a/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/PublicationContractTest.java +++ b/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/PublicationContractTest.java @@ -60,6 +60,51 @@ void mavenUsesTheVerifiedBundleAndCiRunsOnPushAndPullRequest() throws Exception assertTrue(ciWorkflow.contains("--offline")); } + @Test + void releaseTargetsTheAddonAndDeclaresItsRequiredMods() throws Exception { + java.util.Properties metadata = new java.util.Properties(); + metadata.load(new java.io.StringReader(text("gradle.properties"))); + + assertEquals("true", metadata.getProperty("release_enabled")); + assertEquals("1755976", metadata.getProperty("curseforge_project_id")); + + String workflow = text(".github/workflows/deploy-release.yml"); + + assertTrue(workflow.contains("curseforge-dependencies: |")); + assertTrue(workflow.contains("1754162(required)")); + assertTrue(workflow.contains("220318(required)")); + } + + @Test + void releaseRoutingAndCiFollowTheSelectedBranch() throws Exception { + String workflow = text(".github/workflows/deploy-release.yml"); + String ci = text(".github/workflows/ci.yml"); + String tags = text(".github/workflows/release-on-tag.yml"); + + assertTrue(workflow.contains("2) loader_suffix=-neo")); + assertTrue(ci.contains("steps.target.outputs.java_setup_version")); + assertTrue(ci.contains("steps.target.outputs.gradle_java_version")); + assertTrue(ci.contains("artifact_prefix=SkysBuildingPieces-BiomesOPlenty-Addon-$(value mod_version)")); + assertTrue(ci.contains("- 'master-*'")); + assertTrue(tags.contains("s/^minecraft_version=//p")); + assertTrue(tags.contains("s/^loader_code=//p")); + assertFalse(tags.contains("\\.110021$")); + assertFalse(tags.contains("\\.112021$")); + } + + @Test + void mavenBuildsThePinnedCoreWithTheQualifiedJavaToolchain() throws Exception { + String workflow = text(".github/workflows/deploy-release.yml"); + String maven = workflow.substring(workflow.indexOf(" publish_maven:"), + workflow.indexOf(" publish_curseforge:")); + + assertTrue(maven.contains("Pin Java 8 dependency builds")); + assertTrue(maven.contains("core-dependency.properties")); + assertTrue(maven.contains("steps.content.outputs.pieces")); + assertTrue(maven.contains("-PbuildingPiecesCoreDir=dependencies/pieces")); + assertTrue(maven.contains("-PpreparedReleaseDir=")); + } + private String text(String path) throws Exception { return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8).replace("\r\n", "\n"); } diff --git a/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/RepositoryTest.java b/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/RepositoryTest.java index 7cdf476..83b16ed 100644 --- a/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/RepositoryTest.java +++ b/src/test/java/zone/moddev/mc/skysbuildingpiecesbop/RepositoryTest.java @@ -77,7 +77,7 @@ void workflowsBuildTheLockedCoreAndCannotPublish() throws Exception { for (String classifier : new String[] { "", "-sources", "-javadoc" }) { assertTrue( - ciWorkflow.contains("SkysBuildingPieces-BiomesOPlenty-Addon-0.1.2.110021" + classifier + ".jar")); + ciWorkflow.contains("build/libs/${{ steps.target.outputs.artifact_prefix }}" + classifier + ".jar")); } assertTrue(ciWorkflow.contains("-PbuildingPiecesCoreDir=dependencies/pieces"));