A Gradle plugin that takes the boilerplate out of NeoForge mod workspaces built with
NeoGradle. It applies NeoGradle's userdev plugin and
configures most of a mod workspace from gradle.properties: publishing, the generated
neoforge.mods.toml, source sets, runs, tests and mod dependencies.
settings.gradle.kts:
pluginManagement {
plugins {
id("net.neoforged.gradle.userdev") version "7.1.38"
id("at.minecraftschurli.gradle.modhelper") version "2.0"
}
repositories {
gradlePluginPortal()
maven { url = uri("https://maven.neoforged.net/releases") }
maven { url = uri("https://maven.minecraftschurli.at/maven-public") }
}
}The plugin depends on NeoGradle through a version range, so the NeoGradle version is picked by the mod, not by the plugin release.
build.gradle.kts:
plugins {
id("at.minecraftschurli.gradle.modhelper")
}
helper.withApiSourceSet()
helper.withTestSourceSet()
helper.withCommonRuns()| Property | Used for |
|---|---|
mod.group, mod.id, mod.version, mod.name, mod.vendor |
Maven coordinates, archive name, mods.toml |
mod.description, mod.authors, mod.credits, mod.logo, mod.url |
mods.toml |
mod.update_url, mod.mod_url |
mods.toml updateJSONURL / modUrl |
mc_version, mc_version_range |
Version suffix, Minecraft dependency, run/toml layout |
neo_version, neo_version_range |
NeoForge dependency |
java_version |
Java toolchain |
license.name, license.url, license.file |
mods.toml, POM, file bundled in jars |
github.owner, github.repo |
POM scm/issues/CI urls, mods.toml issueTrackerURL |
loader.name, loader.version |
mods.toml modLoader / loaderVersion (optional) |
mc-publish.curseforge, mc-publish.modrinth |
[mc-publish] section for publishing actions |
dependency.<modid>.version, dependency.<modid>.version.range |
Mod dependencies, see below |
junit_version |
JUnit BOM for the test source set |
project_type |
MOD (default) or LIBRARY (uses lib.* instead of mod.*, no mods.toml) |
Every value is also exposed as a property on the helper extension and can be set there instead.
The version is <mc_version>-<mod.version>, suffixed with the RELEASE_TYPE environment variable
(SNAPSHOT if unset, nothing for release). Publishing goes to MAVEN_URL with
MAVEN_USER/MAVEN_PASSWORD, or to build/repo if those aren't set.
| Function | Effect |
|---|---|
withApiSourceSet() |
api source set, published as its own feature variant, included in the main jar, the only source set covered by javadoc |
withDataGenSourceSet() |
data source set, used by the data generation runs |
withTestSourceSet(cfg) |
test source set for JUnit tests, with the NeoForge testframework and a junit run, see Tests |
withGameTestSourceSet() |
gametest source set for game tests |
withClientRun(prefix, cfg) |
client run (<prefix>Client with a prefix) |
withServerRun(prefix, cfg) |
server run (<prefix>Server with a prefix), without gui |
withDataGenRuns(useSeparateSourceSet, cfg) |
clientData + serverData runs (data before 1.21.4), output to src/main/generated, with the data source set unless useSeparateSourceSet = false |
withRenderDocClientRun(prefix, cfg) |
client run with RenderDoc enabled |
withGameTestRuns(cfg) |
gameTestServer run, adds the gametest source set, -ea and the game test namespace to the client and server runs |
withCommonRuns() |
client, server, data generation and game test runs, instead of NeoGradle's default runs |
withJarJar() |
Makes the jar-in-jar jar the main artifact (the plain jar gets the slim classifier) |
Call order doesn't matter. Calling a run function again, e.g. withDataGenRuns { ... } after
withCommonRuns(), only adds its configuration. Every run uses run/ as working directory and
has main as a mod source.
test,dataandgametestinherit main'simplementation,runtimeOnlyandcompileOnlydependencies. Nothing flows back into main.apiinherits nothing from main. OnlymodCompileOnlyreaches it.modCompileOnlyis for things every source set compiles against, includingapi, such as annotations:
dependencies {
"modCompileOnly"("org.jetbrains:annotations:23.0.0")
}NeoForge is added to main's implementation automatically.
-
testholds JUnit tests and runs through NeoGradle'sjunitrun, so./gradlew testruns inside the modded environment. Plain tests run as normal; tests that need registry or datapack data can ask for an ephemeral server:@Test @ExtendWith(EphemeralTestServerProvider.class) void itemsAreRegistered(MinecraftServer server) { assertNotNull(server.registryAccess().lookupOrThrow(Registries.ITEM)); }
-
gametestholds world-based game tests, with their structures and test data insrc/gametest/resources. It has the NeoForge testframework on its classpath, which provides the game test annotations since 26.1 removed vanilla's (@GameTest,@TestHolder,ExtendedGameTestHelper, ...). Run them with./gradlew runGameTestServeror/testin the client.
Mod dependencies are declared once and produce both the [[dependencies.<modid>]] entries in
mods.toml and, if they declare an artifact, the Gradle dependencies and repository.
neoforge and minecraft are always declared. They live in the mods extension of the
dependencies block (helper.dependencies is the same container):
dependencies {
mods.jei()
mods.curios()
mods.required("patchouli") {
ordering = ModDependency.Ordering.BEFORE
artifact("vazkii.patchouli:patchouli-neoforge:{version}", apiClassifier = "api")
repository("https://maven.blamejared.com/")
}
}The version comes from dependency.<modid>.version, and the mods.toml range from
dependency.<modid>.version.range. In artifact notations {mc} and {version} are replaced by the
Minecraft and dependency version. artifact { mc, version -> "..." } works as well.
repository(url) adds a repository restricted to the artifact's group (pass group = "..." to
choose another). It is added as soon as the dependency is declared, so it is also available to
configurations NeoGradle resolves while configuring the project, such as interfaceInjection.
How artifacts are added (to main unless usedBy(...) names other source sets):
| Without api artifact | With api artifact | |
|---|---|---|
required |
implementation |
api to compileOnly, full jar to runtimeOnly |
optional |
compileOnly + run runtime |
api to compileOnly, full jar to run runtime |
- Run runtime means NeoGradle's
localRunRuntimeconfiguration: the jar is on the runs' classpath but on no compile classpath and not in the published POM. It is skipped in CI; setdevRuntimeto override that per dependency. apias a target only ever gets the compile artifact.- Presets
jei(),jade(),curios()andgeckolib()come with coordinates and repositories, and everything can be overridden in their configuration block.theoneprobe()only declares the mods.toml entry.
Declaring returns the ModDependency, so its artifact, apiArtifact and version providers
can be used for anything unusual:
dependencies {
val curios = mods.curios()
val geckolib = mods.geckolib()
"dataRuntimeOnly"(curios.artifact)
"interfaceInjection"(geckolib.artifact)
}generateModsToml writes META-INF/neoforge.mods.toml (mods.toml before 1.20.6) into the jar from
the properties above and these extension properties:
| Property | mods.toml |
|---|---|
accessTransformers |
[[accessTransformers]], defaults to META-INF/accesstransformer.cfg if that file exists, also passed to NeoGradle |
mixin(config) { requiredMods; behaviorVersion } |
[[mixins]] |
featureFlags |
generated META-INF/feature_flags.json (flags are prefixed with the mod id) |
enumExtensions |
enumExtensions |
features |
[features.<modid>], e.g. javaVersion |
modproperties |
[modproperties.<modid>] |
fileProperties |
[properties] |
services, showAsResourcePack, showAsDataPack |
same name |
projectNamespace, projectLogoBlur |
namespace, logoBlur |
Entries that are empty or unset are omitted.
- Manifests: jars get a manifest with the Maven coordinates, Minecraft/NeoForge versions,
FMLModTypeand license. - JSON: JSON resources are minified when they are packaged.
- CI:
setupGithubActionswrites the mod id, version and Minecraft version to$GITHUB_OUTPUTwhen it is set.
./gradlew build # compile, plugin validation, unit tests
./gradlew functionalTest # TestKit tests against real NeoGradle (slow, needs network)To try a change in a mod, run ./gradlew publishToMavenLocal and add mavenLocal() to the mod's
pluginManagement repositories.