Build with loom-back-compat and a Java toolchain per version

Prepares the build for 26.x, which ships unobfuscated and needs Java 25
(#39):

- loom-back-compat applies the remapping Loom to 1.21.x and would apply
  the plain one to 26.x. loom_version becomes loomx.loom_version.
- Each version compiles with its Java toolchain (21 for 1.21.x, 25 for
  26.x), and the mixin configs take their compatibility level from it.
  The foojay resolver downloads a missing JDK; CI installs 21 and 25.
- buildAll collects remapJar's output, or jar's where there's nothing
  to remap.
- fabric.mod.json depends on fabric-api, Fabric API's mod id; 26.x's
  jar no longer provides the old "fabric" alias.

Nothing changes for the 1.21.x jars.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-30 19:15:02 +02:00
co-authored by claude
parent 950a83c48d
commit b018db1851
10 changed files with 52 additions and 32 deletions
+8 -3
View File
@@ -19,15 +19,20 @@ jobs:
# The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's
# dependencies, Loom's Minecraft cache and the build cache in the runner's
# actions cache.
- name: Set up Java 21
# 1.21.x builds with Java 21 and 26.x with Java 25. The last version listed is the default
# (Gradle runs on it), and the Gradle commands below point toolchain detection at both.
- name: Set up Java 25 and 21
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
java-version: |
25
21
cache: gradle
- name: Compile all versions
run: |
chmod +x gradlew
# --continue reports every version that fails, not just the first.
./gradlew compileJava --no-daemon --stacktrace --continue
./gradlew compileJava --no-daemon --stacktrace --continue \
-Porg.gradle.java.installations.fromEnv=JAVA_HOME_21_X64,JAVA_HOME_25_X64
+8 -3
View File
@@ -47,11 +47,15 @@ jobs:
# The JDK comes from the runner's tool cache. `cache: gradle` stores Gradle's
# dependencies, Loom's Minecraft cache and the build cache in the runner's
# actions cache.
- name: Set up Java 21
# 1.21.x builds with Java 21 and 26.x with Java 25. The last version listed is the default
# (Gradle runs on it), and the Gradle commands below point toolchain detection at both.
- name: Set up Java 25 and 21
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
java-version: |
25
21
cache: gradle
- name: Build all versions
@@ -59,7 +63,8 @@ jobs:
chmod +x gradlew
# buildAll remaps every Minecraft version (two at a time, to stay within the
# Gradle heap) into build/allJars.
./gradlew buildAll --no-daemon --stacktrace
./gradlew buildAll --no-daemon --stacktrace \
-Porg.gradle.java.installations.fromEnv=JAVA_HOME_21_X64,JAVA_HOME_25_X64
- name: Upload jars to the release
env:
+10 -7
View File
@@ -1,11 +1,14 @@
// Builds one Minecraft version from the shared src/. Stonecutter runs this for each version in
// stonecutterVersions (settings.gradle.kts), with that version's versions/<v>/gradle.properties.
plugins {
id("net.fabricmc.fabric-loom-remap")
id("dev.kikugie.loom-back-compat")
`maven-publish`
}
val minecraftVersion = property("minecraft_version") as String
// The Minecraft versions the jar loads on (fabric.mod.json); a range where hotfixes share a jar.
val minecraftCompat = findProperty("minecraft_compat") as String? ?: minecraftVersion
val javaVersion = if (sc.current.parsed >= "26.1") 25 else 21
val loaderVersion = property("loader_version") as String
version = "${property("mod_version")}+$minecraftVersion"
@@ -17,7 +20,7 @@ base {
dependencies {
minecraft("com.mojang:minecraft:$minecraftVersion")
mappings(loom.officialMojangMappings())
loomx.applyMojangMappings()
modImplementation("net.fabricmc:fabric-loader:$loaderVersion")
modImplementation("net.fabricmc.fabric-api:fabric-api:${property("fabric_api_version")}")
@@ -42,23 +45,23 @@ dependencies {
tasks.processResources {
val values = mapOf(
"version" to project.version.toString(),
"minecraft_version" to minecraftVersion,
"minecraft_version" to minecraftCompat,
"loader_version" to loaderVersion,
"java" to "JAVA_$javaVersion",
)
inputs.properties(values)
filteringCharset = "UTF-8"
filesMatching("fabric.mod.json") { expand(values) }
filesMatching(listOf("fabric.mod.json", "*.mixins.json")) { expand(values) }
}
tasks.withType<JavaCompile>().configureEach {
options.release = 21
options.release = javaVersion
}
java {
// Loom attaches the sources jar to remapSourcesJar and build.
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
toolchain.languageVersion = JavaLanguageVersion.of(javaVersion)
}
tasks.jar {
+2 -2
View File
@@ -11,5 +11,5 @@ mod_version=0.1.4-beta
# Saturn's emote library (git.selimaj.dev/saturnclientmc/emotes)
emotes_version=0.1.2
# Loom, for every Minecraft version (read in settings.gradle.kts).
loom_version=1.17.21
# Loom, for every Minecraft version (read by loom-back-compat).
loomx.loom_version=1.17.21
+8 -7
View File
@@ -5,16 +5,15 @@ pluginManagement {
gradlePluginPortal()
mavenCentral()
}
// Loom's version for build.gradle.kts, which can't read properties in its plugins block.
val loom_version: String by settings
plugins {
id("net.fabricmc.fabric-loom-remap") version loom_version
}
}
plugins {
// Downloads the Java toolchain a version needs (Java 25 for 26.1+) when it isn't installed.
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
id("dev.kikugie.stonecutter") version "0.9.8"
// Picks the Loom variant per version: remapping for 1.21.x, plain for the unobfuscated 26.1+.
// Its version is loomx.loom_version in gradle.properties.
id("dev.kikugie.loom-back-compat") version "0.4.2"
}
rootProject.name = "saturn-client"
@@ -23,7 +22,9 @@ include("common")
// Versions built by Stonecutter from the shared src/ (see stonecutter.gradle.kts). Each one's
// versions/<v>/gradle.properties holds its Minecraft, Fabric Loader and Fabric API versions.
val stonecutterVersions = listOf("1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11")
val stonecutterVersions = listOf(
"1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11",
)
stonecutter {
create(rootProject) {
+1 -1
View File
@@ -22,7 +22,7 @@
],
"depends": {
"fabricloader": ">=${loader_version}",
"fabric": "*",
"fabric-api": "*",
"minecraft": "${minecraft_version}"
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
"required": true,
"minVersion": "0.8",
"package": "org.saturnclient.impl.features.mixins",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "${java}",
"client": [
"render.GameRendererMixin",
"render.LightmapTextureManagerMixin",
+1 -1
View File
@@ -2,7 +2,7 @@
"required": true,
"minVersion": "0.8",
"package": "org.saturnclient.impl.mixins",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "${java}",
"client": [
"IdentifierMixin",
"ItemStackMixin",
+3 -2
View File
@@ -2,14 +2,15 @@
"required": true,
"minVersion": "0.8",
"package": "org.saturnclient.saturnclient.mixin",
"compatibilityLevel": "JAVA_21",
"compatibilityLevel": "${java}",
"client": [
"TabListMixin",
"PlayerEntityRendererMixin",
"WhoAmI",
"DrawContextAccessor",
"SplashOverlayMixin",
"SetScreenMixin"
"SetScreenMixin",
"GuiRendererMixin"
],
"injectors": {
"defaultRequire": 1
+10 -5
View File
@@ -69,17 +69,22 @@ subprojects {
tasks.register("buildAll") {
group = "build"
description = "Builds every Minecraft version's remapped jar and collects them in build/allJars"
description = "Builds every Minecraft version's mod jar and collects them in build/allJars"
val remapJars = subprojects.flatMap { it.tasks.matching { task -> task.name == "remapJar" } }
dependsOn(remapJars)
// The mod jar is remapJar's output for 1.21.x, and jar's for 26.1+, which isn't obfuscated.
val modJars = subprojects.filter { it.name != "common" }.flatMap { project ->
val unobfuscated = project.name.substringBefore('.').toInt() >= 26
val task = if (unobfuscated) "jar" else "remapJar"
project.tasks.matching { it.name == task }
}
dependsOn(modJars)
doLast {
val outputDir = layout.buildDirectory.dir("allJars").get().asFile
outputDir.mkdirs()
remapJars.map { (it as AbstractArchiveTask).archiveFile.get().asFile }
modJars.map { (it as AbstractArchiveTask).archiveFile.get().asFile }
.filter { it.exists() && !it.name.contains("sources") }
.forEach { it.copyTo(outputDir.resolve(it.name), overwrite = true) }
println("All remapped jars collected in: $outputDir")
println("All mod jars collected in: $outputDir")
}
}