- Java 100%
Adds actions/cache@v4 for ~/.gradle/caches and ~/.gradle/wrapper in every job that runs Gradle (build + publish-mod matrix + publish-maven). Cold runs still download everything; subsequent runs restore the Loom/ForgeGradle/ NeoForge dependency set from the runner cache instead of re-fetching. [skip ci] |
||
|---|---|---|
| .forgejo/workflows | ||
| .run | ||
| common-1.20.1 | ||
| common-1.21.1 | ||
| common-26.1 | ||
| fabric-1.20.1 | ||
| fabric-1.21.1 | ||
| fabric-26.1 | ||
| forge-1.20.1 | ||
| gradle/wrapper | ||
| neoforge-1.21.1 | ||
| neoforge-26.1 | ||
| .gitattributes | ||
| .gitignore | ||
| build.gradle | ||
| CHANGELOG.md | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| README.md | ||
| settings.gradle | ||
Multi-Version-Loader-Template
Template for multi-version, multi-loader Minecraft mods. One repo builds:
| MC | Forge | Fabric | NeoForge | Java |
|---|---|---|---|---|
| 1.20.1 | yes | yes | no | 17 |
| 1.21.1 | no | yes | yes | 21 |
| 26.1.x | no | yes | yes | 25 |
Layout
common-<mcver>/ shared, loader-agnostic code (source-only, compiled by each loader module)
fabric-<mcver>/ Fabric entrypoint + platform impl
forge-1.20.1/ Forge entrypoint (standalone Gradle 8.8 build, see below)
neoforge-<mcver>/ NeoForge entrypoint (1.21.1+)
Common code never touches loader APIs. Anything loader-specific goes behind the
PlatformHelper ServiceLoader interface in common-*/.../platform/ — each loader module
provides an implementation and registers it in META-INF/services/. Add more capabilities
(networking, config, events) the same way.
The loader modules don't depend on the common jar — they add the common module's source directories to their own source set and compile everything together, so there is no runtime common artifact to ship.
forge-1.20.1 is a separate Gradle build
ForgeGradle 6 can't run on Gradle 9 and includeBuild can't bridge Gradle versions, so
forge-1.20.1/ has its own Gradle 8.8 wrapper and is not in settings.gradle.
- CLI:
./gradlew forge1201Build/forge1201RunClient/forge1201RunServerfrom the root (delegates toforge-1.20.1/gradlewvia Exec). - IntelliJ: Gradle tool window →
+→ Link Gradle Project → pickforge-1.20.1/build.gradle. It appears as a second root with its own task tree and run configs. - Its
gradle.propertiesduplicates the mod properties — keep it in sync with the root one.
Commands
./gradlew build # everything except forge-1.20.1
./gradlew forge1201Build # forge-1.20.1
./gradlew :fabric-1.20.1:runClient
./gradlew :fabric-1.21.1:runClient
./gradlew :fabric-26.1:runClient
./gradlew :neoforge-1.21.1:runClient
./gradlew :neoforge-26.1:runClient
./gradlew forge1201RunClient
Jars land in <module>/build/libs/.
IntelliJ users: .run/ ships shared run configurations for every loader's runClient,
so they show up in the run dropdown as soon as the project is opened.
CI / publishing
Forgejo Actions workflows in .forgejo/workflows/:
build.yml— builds all six loader jars on every push/PR and stashes them as artifacts.publish.yml— on av*tag: builds the matrix, publishes to Modrinth/CurseForge (mc-publish), publishes all modules to the Forgejo Maven, creates a Forgejo release with the six jars attached, and announces it via the Discord bot. Aworkflow_dispatchrun stops after the build step (dry run).
Both read mod_id/mod_name/mod_group_id from gradle.properties at runtime, so they
survive the rename checklist untouched. Required repo secrets for publishing:
MODRINTH_PROJECT_ID, MODRINTH_TOKEN, CURSEFORGE_PROJECT_ID, CURSEFORGE_TOKEN,
MAVEN_PUBLISH_TOKEN, DISCORD_RELEASE_WEBHOOK_SECRET. Delete the steps/jobs a project
doesn't use (e.g. notify-discord, or the mc-publish step before the first public release).
Release notes come from CHANGELOG.md: keep one ## vX.Y.Z section per release — the
pipeline extracts the section matching the pushed tag.
Starting a new mod from this template
gradle.properties(root andforge-1.20.1/gradle.properties): setmod_id,mod_name,mod_version,mod_group_id,mod_authors,mod_description,mod_license.- Rename the package
net.tysontheember.examplemod→ your package root in every module (common-*,fabric-*,forge-1.20.1,neoforge-*). - Rename the classes:
ExampleMod,ExampleModFabric,ExampleModFabricClient,ExampleModForge,ExampleModNeoForge, andMOD_ID/MOD_NAMEinConstants. - Rename the mixin configs to match the new mod id — they are referenced by file name:
common-*/src/main/resources/examplemod.mixins.jsonfabric-*/src/main/resources/examplemod-fabric.mixins.jsonforge-1.20.1/src/main/resources/examplemod-forge.mixins.jsonneoforge-*/src/main/resources/examplemod.mixins.jsonUpdate thepackageandrefmapfields inside them too.
- Rename the ServiceLoader registration files (file name = interface FQN) and update the
implementation FQN inside:
*/src/main/resources/META-INF/services/net.tysontheember.examplemod.platform.PlatformHelper - Update the entrypoint class names in each
fabric-*/src/main/resources/fabric.mod.json. settings.gradle: setrootProject.name.- Update
pack.mcmetadescriptions, resetCHANGELOG.md, and drop in yourLICENSE. - Delete the example
TitleScreenMixin(all threecommon-*modules) once you have real mixins, or keep it to verify the pipeline. ./gradlew build && ./gradlew forge1201Buildto confirm everything still compiles.
Version notes
- Versions are pinned in
gradle.properties; 1.20.1 Forge versions live inforge-1.20.1/gradle.properties. - Fabric 1.20.1/1.21.1 use the
fabric-loom-remapplugin with Mojang mappings — the stock plugin refuses Mojang mappings in a non-obfuscated multi-loader workspace. MC 26.1 is unobfuscated, sofabric-26.1uses stockfabric-loomwith no mappings at all. - API deltas between versions stay in the per-version modules. Example:
FMLEnvironment.dist/production(NeoForge 1.21.1) becameFMLEnvironment.getDist()/isProduction()(NeoForge 26.1) — seeNeoForgePlatformHelper. - Mixin
compatibilityLevelper version: JAVA_17 (1.20.1), JAVA_21 (1.21.1), JAVA_25 (26.1). 26.1 configs have norefmap(unobfuscated runtime). - Publishing:
publishpushes tomavenRepoPath(defaults tobuild/maven-repo) and to the Forgejo maven ifforgejoActor/forgejoToken(or the env vars) are set.
Credits
Structure follows jaredlll08/MultiLoader-Template, adapted for multi-version with the patterns from EmbersTextAPI.