Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 41 additions & 26 deletions .github/workflows/deploy-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ jobs:
java_toolchain_version="$(value java_toolchain_version)"
java_setup_version="$(value java_setup_version)"
gradle_java_version="$(value gradle_java_version)"
gradle_java_setup_version="$(value gradle_java_setup_version)"
curseforge_project_id="$(value curseforge_project_id)"
cf_requirements="$(value cf_requirements)"
orespawn_project_id="$(value orespawn_curse_project_id)"

for required in release_version minecraft_version loader_name loader_code \
java_version gradle_java_version curseforge_project_id; do
Expand Down Expand Up @@ -237,28 +239,30 @@ jobs:
echo "Requested version $REQUESTED_VERSION does not match ${{ steps.route.outputs.target_branch }} mod_version $release_version" >&2
exit 1
fi
case "${{ steps.route.outputs.target_branch }}" in
master-1.12)
gradle_java_setup_version=17.0.1+12
if [[ "$minecraft_version" != '1.12.2' || "$java_toolchain_version" != '8.0.502+7' \
|| "$gradle_java_version" != '17' ]]; then
echo "master-1.12 has an unexpected target/toolchain contract" >&2
exit 1
fi
;;
master-1.18)
if [[ -z "$gradle_java_setup_version" ]]; then
if [[ "$install_gradle_java" == false ]]; then
gradle_java_setup_version="$java_setup_version"
if [[ "$minecraft_version" != '1.18.2' || "$java_toolchain_version" != '17.0.1+12' \
|| "$gradle_java_version" != '17' ]]; then
echo "master-1.18 has an unexpected target/toolchain contract" >&2
exit 1
fi
;;
*)
echo "This dispatcher supports Base Metals master-1.12 and master-1.18" >&2
exit 1
;;
esac
elif [[ "$gradle_java_version" == 17 ]]; then
# Legacy targets keep the same pinned Gradle JDK as their CI builds.
gradle_java_setup_version=17.0.1+12
else
gradle_java_setup_version="$gradle_java_version"
fi
fi
if [[ "$(java_major "$java_toolchain_version")" != "$java_version" \
|| "$(java_major "$gradle_java_setup_version")" != "$gradle_java_version" ]]; then
echo "Java setup selectors do not match the declared game and Gradle Java versions" >&2
exit 1
fi

if [[ -n "$orespawn_project_id" && "$orespawn_project_id" != 245586 ]]; then
echo "Unexpected OreSpawn CurseForge project $orespawn_project_id" >&2
exit 1
fi
if [[ -z "$cf_requirements" && "$orespawn_project_id" == 245586 ]]; then
# MMDLib-free ports declare their required OreSpawn dependency directly.
cf_requirements=mmd-orespawn
fi

curseforge_dependencies=()
IFS=',' read -ra requirement_slugs <<<"$cf_requirements"
Expand All @@ -270,8 +274,8 @@ jobs:
*) echo "Unsupported required CurseForge relation $slug" >&2; exit 1 ;;
esac
done
if (( ${#curseforge_dependencies[@]} == 0 )); then
echo "No required CurseForge relations were declared" >&2
if [[ " ${curseforge_dependencies[*]} " != *' 245586(required) '* ]]; then
echo "The required OreSpawn CurseForge relation was not declared" >&2
exit 1
fi

Expand Down Expand Up @@ -405,6 +409,8 @@ jobs:
echo "property=$property" >> "$GITHUB_OUTPUT"

- name: Build, test, and audit once
env:
LOADER_NAME: ${{ needs.preflight.outputs.loader_name }}
run: |
set -euo pipefail
chmod +x ./gradlew
Expand All @@ -414,9 +420,18 @@ jobs:
-Dorg.gradle.java.installations.auto-download=false
)
dependency_argument="-P${{ steps.dependencies.outputs.property }}=${{ steps.dependencies.outputs.repository }}"
./gradlew clean check build javadoc verifyReleaseArtifacts writeReleaseChecksums \
"$dependency_argument" \
--no-daemon --stacktrace "${java_args[@]}"
if [[ "$LOADER_NAME" == neoforge ]]; then
# NeoGradle prepares inputs in build/tmp during configuration. Clean first.
./gradlew clean "$dependency_argument" \
--no-daemon --stacktrace --max-workers=2 "${java_args[@]}"
./gradlew check build javadoc verifyReleaseArtifacts writeReleaseChecksums \
"$dependency_argument" \
--no-daemon --stacktrace --max-workers=2 "${java_args[@]}"
else
./gradlew clean check build javadoc verifyReleaseArtifacts writeReleaseChecksums \
"$dependency_argument" \
--no-daemon --stacktrace "${java_args[@]}"
fi

- name: Stage the immutable release artifact
id: artifact
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ toolchain and dependency versions are recorded in `gradle.properties`.
`prepareEclipse` creates client and server launches with processed resources
and keeps optional compile-only mods and test code out of normal launches.
The [build-script guide](gradle/README.md) explains the supporting scripts.
The [release guide](docs/releasing.md) covers publication from the MMD Actions tab.

Release artifacts use the Maven coordinates
`zone.moddev.mc.basemetals:BaseMetals:2.6.0.112021`. Java packages remain under
Expand Down
53 changes: 53 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Releasing Base Metals

Run **Release Base Metals** in the MMD repository's Actions tab. The workflow
lives on the default branch, but builds the selected Minecraft branch rather
than the default branch's mod.

Enter the full four-part Base Metals version, choose the CurseForge release
level, and confirm publication. For example, `3.0.1.113021` selects Minecraft
1.13.2 with Forge and releases `master-1.13.2`.

## Branches and version numbers

The final version component encodes Minecraft's major version, two-digit minor
version, two-digit patch version, and a loader digit: `1` for Forge or `2` for
NeoForge. The dispatcher looks for `master-<major>.<minor>.<patch>` first, then
`master-<major>.<minor>`. NeoForge branches have the suffix `-neo`.

This follows OreSpawn's branch convention, including newer `26.x` versions.
There is no list of allowed Minecraft versions to update when adding a port.
The selected branch must exist and its metadata must match the requested
release. Old branches without the modern build and release checks cannot be
released through this workflow.

## Preparing a target branch

Each port needs:

- Matching `mod_version`, `minecraft_version`, `loader_name`, and `loader_code`
in `gradle.properties`, with `curseforge_project_id=240967`.
- `java_version` for Minecraft, `java_toolchain_version` for compilation, and
`gradle_java_version` for Gradle. `java_setup_version` can specify the hosted
JDK download selector. `gradle_java_setup_version` can pin a separate Gradle
JDK; legacy Java 8 and 16 ports default to Temurin `17.0.1+12` for Gradle.
- An exact, checksum-verified OreSpawn dependency and either
`gradle/stage-orespawn-release.sh` or the legacy dependency-staging script.
- `check`, `build`, `javadoc`, `verifyReleaseArtifacts`, `writeReleaseChecksums`,
and the prepared-artifact Maven publication used by the existing ports.
- A successful **Build, test, and audit** check on the exact release commit.

Required CurseForge dependencies come from `cf_requirements`. MMDLib-free ports
may instead declare `orespawn_curse_project_id=245586`, which makes OreSpawn
required without adding MMDLib. The 1.12 branch still requires both mods.

## Publication

The workflow builds and tests once, then records checksums for the main,
sources, and Javadoc jars. It publishes those same files to MMD Maven,
CurseForge, and finally GitHub Releases. An existing tag must point to the
validated commit; the workflow never moves it to another commit.

Publication requires `MAVEN_UPLOAD_URL`, `MAVEN_UPLOAD_USERNAME`,
`MAVEN_UPLOAD_PASSWORD`, and `CURSEFORGE_TOKEN` in the MMD repository. Forks can
run CI but cannot publish through this dispatcher.
3 changes: 3 additions & 0 deletions gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ of that main path:
- `ide/eclipse.gradle` generates and verifies isolated Eclipse launches.
- `verification/workflows.gradle` checks the pinned GitHub Actions contracts.

The [release guide](../docs/releasing.md) explains how the default-branch
dispatcher selects and publishes a Minecraft target.

Scripts share settings through small immutable maps rather than relying on
variables from another script's scope.

Expand Down
13 changes: 12 additions & 1 deletion gradle/verification/workflows.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ tasks.register('verifyWorkflowContracts') {
'deploy-release.yml', '**Nothing has been published.**'
],
'deploy-release.yml': [
'master-1.12)', 'master-1.18)',
'master-$mc_major.$mc_minor.$mc_patch$loader_suffix',
'master-$mc_major.$mc_minor$loader_suffix',
'stage-legacy-dependencies.sh', 'stage-orespawn-release.sh',
"mmdlib) curseforge_dependencies+=('261744(required)')",
'cf_requirements=mmd-orespawn',
'gradle_java_setup_version=17.0.1+12',
'java-version: ${{ needs.preflight.outputs.gradle_java_setup_version }}',
'if [[ "$LOADER_NAME" == neoforge ]]',
'confirm_live_publication'
]
]
Expand All @@ -38,6 +41,10 @@ tasks.register('verifyWorkflowContracts') {
throw new GradleException("Missing workflow ${name}")
}
String text = workflow.getText('UTF-8').replace('\r\n', '\n')
if (name == 'deploy-release.yml'
&& text.contains('This dispatcher supports Base Metals master-1.12 and master-1.18')) {
throw new GradleException('The release dispatcher must not limit releases to two branches')
}
List<String> missing = fragments.findAll { !text.contains(it) }
if (!missing.isEmpty()) {
throw new GradleException("${name} is missing contract fragments ${missing}")
Expand All @@ -55,3 +62,7 @@ tasks.register('verifyWorkflowContracts') {
tasks.named('check') {
dependsOn tasks.named('verifyWorkflowContracts')
}

tasks.named('test') {
inputs.file('.github/workflows/deploy-release.yml')
}
Loading
Loading