Skip to content

Instantly share code, notes, and snippets.

@isXander
Last active May 16, 2026 01:37
Show Gist options
  • Select an option

  • Save isXander/45e8fe49c7232a9a804de669f1151ccd to your computer and use it in GitHub Desktop.

Select an option

Save isXander/45e8fe49c7232a9a804de669f1151ccd to your computer and use it in GitHub Desktop.
multi-loader, single-project
plugins {
`java-library`
id("net.fabricmc.fabric-loom") version "1.17.0-alpha.8"
id("net.neoforged.gradle.userdev") version "7.1.27"
`maven-publish`
}
group = "dev.isxander"
version = "1.0.4"
val fabric by sourceSets.registering
val neoforge by sourceSets.registering
java {
withSourcesJar()
registerFeature("fabric") {
usingSourceSet(fabric.get())
withSourcesJar()
}
registerFeature("neoforge") {
usingSourceSet(neoforge.get())
withSourcesJar()
}
}
// These configurations are shared between all source sets
// They should not be used for mod-like dependencies where different variants
// would be required across sourcesets
val commonCompileOnly by configurations.registering
val commonRuntimeOnly by configurations.registering
val commonImplementation by configurations.registering
val commonApi by configurations.registering
val commonCompileOnlyApi by configurations.registering
val commonAnnotationProcessor by configurations.registering
// Share the common source set's sources/resources with loader source sets.
// Reference the source set directly — routing through resolvable configurations
// in the same project yields an empty resolution because no incoming dependency exists.
val commonJava = sourceSets.main.map { it.java }
val commonResources = sourceSets.main.map { it.resources }
configurations {
compileOnly { extendsFrom(commonCompileOnly) }
runtimeOnly { extendsFrom(commonRuntimeOnly) }
implementation { extendsFrom(commonImplementation) }
api { extendsFrom(commonApi) }
compileOnlyApi { extendsFrom(commonCompileOnlyApi) }
annotationProcessor { extendsFrom(commonAnnotationProcessor) }
"fabricCompileOnly" { extendsFrom(commonCompileOnly) }
"fabricRuntimeOnly" { extendsFrom(commonRuntimeOnly) }
"fabricImplementation" { extendsFrom(commonImplementation) }
"fabricApi" { extendsFrom(commonApi) }
"fabricCompileOnlyApi" { extendsFrom(commonCompileOnlyApi) }
"fabricAnnotationProcessor" { extendsFrom(commonAnnotationProcessor) }
afterEvaluate {
"fabricCompileClasspath" { extendsFrom(named("minecraftNamedCompile")) }
"fabricRuntimeClasspath" { extendsFrom(named("minecraftNamedRuntime")) }
}
"neoforgeCompileOnly" { extendsFrom(commonCompileOnly) }
"neoforgeRuntimeOnly" { extendsFrom(commonRuntimeOnly) }
"neoforgeImplementation" { extendsFrom(commonImplementation) }
"neoforgeApi" { extendsFrom(commonApi) }
"neoforgeCompileOnlyApi" { extendsFrom(commonCompileOnlyApi) }
"neoforgeAnnotationProcessor" { extendsFrom(commonAnnotationProcessor) }
}
dependencies {
minecraft("com.mojang:minecraft:26.1.2")
// need fabric loader in main sourceset to compile against Mixin.
compileOnly("net.fabricmc:fabric-loader:0.19.2")
// example common
commonImplementation("net.fabricmc:mapping-io:0.8.0")
"fabricImplementation"("net.fabricmc:fabric-loader:0.19.2")
"fabricImplementation"("net.fabricmc.fabric-api:fabric-api:0.149.0+26.1.2")
"fabricCompileOnly"(sourceSets.main.get().output)
"neoforgeImplementation"("net.neoforged:neoforge:26.1.2.50-beta")
"neoforgeCompileOnly"(sourceSets.main.get().output)
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
// include common sourceset in loader-specific compilation and resource processing
// this allows for compile-time guarantees when NeoForge patches a vanilla method signature like a bitch
tasks.named<JavaCompile>("compileFabricJava") {
dependsOn(commonJava)
source(commonJava)
}
tasks.named<Jar>("fabricSourcesJar") {
dependsOn(commonJava)
from(commonJava)
}
tasks.named<JavaCompile>("compileNeoforgeJava") {
dependsOn(commonJava)
source(commonJava)
}
tasks.named<Jar>("neoforgeSourcesJar") {
dependsOn(commonJava)
from(commonJava)
}
tasks.named<ProcessResources>("processFabricResources") {
dependsOn(commonResources)
from(commonResources)
}
tasks.named<ProcessResources>("processNeoforgeResources") {
dependsOn(commonResources)
from(commonResources)
}
// Create a universal jar that includes both loaders
val universalJar by tasks.registering(Jar::class) {
archiveClassifier = "universal"
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(sourceSets.main.get().output)
from(fabric.get().output)
from(neoforge.get().output)
}
val universalSourcesJar by tasks.registering(Jar::class) {
archiveClassifier = "universal-sources"
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(sourceSets.main.get().allSource)
from(fabric.get().allSource)
from(neoforge.get().allSource)
}
// Ensure the jars are generated on `build`
tasks.assemble {
dependsOn("fabricJar", "neoforgeJar", universalJar, universalSourcesJar)
}
val modLoaderAttribute = Attribute.of("io.github.mcgradleconventions.loader", String::class.java)
fun ConfigurationContainer.configureLoaderVariant(
sourceSet: SourceSet,
action: Action<in Configuration>,
) {
listOf(
sourceSet.apiElementsConfigurationName,
sourceSet.runtimeElementsConfigurationName,
sourceSet.sourcesElementsConfigurationName,
//sourceSet.javadocElementsConfigurationName,
).forEach { configurationName ->
named(configurationName, action)
}
}
configurations {
configureLoaderVariant(sourceSets.main.get()) {
attributes {
attribute(modLoaderAttribute, "common")
}
outgoing.capability(provider { "${project.group}:${project.name}:${project.version}" })
outgoing.capability(provider { "${project.group}:${project.name}-common:${project.version}" })
}
configureLoaderVariant(fabric.get()) {
attributes {
attribute(modLoaderAttribute, "fabric")
}
// fabric capability defined by the feature
outgoing.capability(provider { "${project.group}:${project.name}:${project.version}" })
}
configureLoaderVariant(neoforge.get()) {
attributes {
attribute(modLoaderAttribute, "neoforge")
}
// neoforge capability defined by the feature
outgoing.capability(provider { "${project.group}:${project.name}:${project.version}" })
}
}
publishing {
repositories {
mavenLocal()
}
publications {
create<MavenPublication>("mavenJava") {
from(components["java"])
}
}
}
{
"formatVersion": "1.1",
"component": {
"group": "dev.isxander",
"module": "testmod",
"version": "1.0.4",
"attributes": {
"org.gradle.status": "release"
}
},
"createdBy": {
"gradle": {
"version": "9.5.1"
}
},
"variants": [
{
"name": "apiElements",
"attributes": {
"io.github.mcgradleconventions.loader": "common",
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 25,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "testmod-1.0.4.jar",
"url": "testmod-1.0.4.jar",
"size": 1347,
"sha512": "c8e70ea09767f4f2d93105706cb60478c848c1a48aa1b87c11089e420a1c0f170ba6959944c47582c2d409fd6b935f6553c89df4d8d4410c27671d4a63e76510",
"sha256": "1efb9063d8aec0d8cfc446e4172340eab2ce8eb5f1c38823bb4c3e3e51f4b16d",
"sha1": "37546da2b6f030f843b1339b9ecc12ed73302014",
"md5": "10e84cbb07e4a08c21007016bc732561"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-common",
"version": "1.0.4"
}
]
},
{
"name": "runtimeElements",
"attributes": {
"io.github.mcgradleconventions.loader": "common",
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 25,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"dependencies": [
{
"group": "net.fabricmc",
"module": "mapping-io",
"version": {
"requires": "0.8.0"
}
}
],
"files": [
{
"name": "testmod-1.0.4.jar",
"url": "testmod-1.0.4.jar",
"size": 1347,
"sha512": "c8e70ea09767f4f2d93105706cb60478c848c1a48aa1b87c11089e420a1c0f170ba6959944c47582c2d409fd6b935f6553c89df4d8d4410c27671d4a63e76510",
"sha256": "1efb9063d8aec0d8cfc446e4172340eab2ce8eb5f1c38823bb4c3e3e51f4b16d",
"sha1": "37546da2b6f030f843b1339b9ecc12ed73302014",
"md5": "10e84cbb07e4a08c21007016bc732561"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-common",
"version": "1.0.4"
}
]
},
{
"name": "sourcesElements",
"attributes": {
"io.github.mcgradleconventions.loader": "common",
"org.gradle.category": "documentation",
"org.gradle.dependency.bundling": "external",
"org.gradle.docstype": "sources",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "testmod-1.0.4-sources.jar",
"url": "testmod-1.0.4-sources.jar",
"size": 928,
"sha512": "73bdff4789d48010552d43e2e1e761f6cd8daf29cfda5f9473432ada613a598476801d8a61b79b35b68bff72ec0123cc554b1e33bcb8a711cecf9271c47b7649",
"sha256": "b406d595a774280198a540dcee49610bacdbd42feb4cef31fb53fcfbbd732c48",
"sha1": "004322258ac127eb53fdf5cae6947bc0e7360180",
"md5": "0ca52e4ddad8778e73d2cc5d43f2a9b3"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-common",
"version": "1.0.4"
}
]
},
{
"name": "fabricSourcesElements",
"attributes": {
"io.github.mcgradleconventions.loader": "fabric",
"org.gradle.category": "documentation",
"org.gradle.dependency.bundling": "external",
"org.gradle.docstype": "sources",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "testmod-1.0.4-fabric-sources.jar",
"url": "testmod-1.0.4-fabric-sources.jar",
"size": 1358,
"sha512": "209e6551d1b55546d2c5860f74f93e1762c59110cc5916f30baad5036251b2021be66f2edc38e473d42ca794d2d834a12c330e7c9217ef7d4ac0dc5d4d60033c",
"sha256": "71633775026bc8d433369a6f637b93cc69ca3be62d40808bcab8b33336fea9ce",
"sha1": "356cee3fe6ed122e2ed114e715217a72f3d24865",
"md5": "9c18811d1f69965db2dcd33e98a81ab1"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-fabric",
"version": "1.0.4"
}
]
},
{
"name": "fabricApiElements",
"attributes": {
"io.github.mcgradleconventions.loader": "fabric",
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 25,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "testmod-1.0.4-fabric.jar",
"url": "testmod-1.0.4-fabric.jar",
"size": 1909,
"sha512": "cc54eeacbb544fb27ed85cebf4972c9eb39e71a546a457308a93f9d789caeb31c0834927980fe95b4a2e0ed5e21e708ff877098764fa64e1ce1881d52aadeba9",
"sha256": "2980ab707568378786e0b97b7ecf34978fc4504eb30dcb6fde259e957835e831",
"sha1": "36600ceaf906da86d2dc960289becaa892e1682d",
"md5": "d2a19185a860ee2c521549991ba8dcce"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-fabric",
"version": "1.0.4"
}
]
},
{
"name": "fabricRuntimeElements",
"attributes": {
"io.github.mcgradleconventions.loader": "fabric",
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 25,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"dependencies": [
{
"group": "net.fabricmc",
"module": "fabric-loader",
"version": {
"requires": "0.19.2"
}
},
{
"group": "net.fabricmc.fabric-api",
"module": "fabric-api",
"version": {
"requires": "0.149.0+26.1.2"
}
},
{
"group": "net.fabricmc",
"module": "mapping-io",
"version": {
"requires": "0.8.0"
}
}
],
"files": [
{
"name": "testmod-1.0.4-fabric.jar",
"url": "testmod-1.0.4-fabric.jar",
"size": 1909,
"sha512": "cc54eeacbb544fb27ed85cebf4972c9eb39e71a546a457308a93f9d789caeb31c0834927980fe95b4a2e0ed5e21e708ff877098764fa64e1ce1881d52aadeba9",
"sha256": "2980ab707568378786e0b97b7ecf34978fc4504eb30dcb6fde259e957835e831",
"sha1": "36600ceaf906da86d2dc960289becaa892e1682d",
"md5": "d2a19185a860ee2c521549991ba8dcce"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-fabric",
"version": "1.0.4"
}
]
},
{
"name": "neoforgeSourcesElements",
"attributes": {
"io.github.mcgradleconventions.loader": "neoforge",
"org.gradle.category": "documentation",
"org.gradle.dependency.bundling": "external",
"org.gradle.docstype": "sources",
"org.gradle.usage": "java-runtime"
},
"files": [
{
"name": "testmod-1.0.4-neoforge-sources.jar",
"url": "testmod-1.0.4-neoforge-sources.jar",
"size": 1359,
"sha512": "974a3a5425536d9682cad8729167a6e7f93dd435089591ee87e80a40a1bc934283a963b58e38251b3107e9356a46bacbc524b874ff90fab20b7e9d5224e9f6ff",
"sha256": "1026ad000b9ac294aac4c7b5d9610958f8ad346c606bd23c36bcdbeb037d21e1",
"sha1": "b69208741aa4ef31e2fef7696d178b9731adceed",
"md5": "a47d24a062b17845a8e854d28e7a0212"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-neoforge",
"version": "1.0.4"
}
]
},
{
"name": "neoforgeApiElements",
"attributes": {
"io.github.mcgradleconventions.loader": "neoforge",
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 25,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-api"
},
"files": [
{
"name": "testmod-1.0.4-neoforge.jar",
"url": "testmod-1.0.4-neoforge.jar",
"size": 1943,
"sha512": "6d99e94ea072c676054662777708eaa5c4375ae0f8c2f6e3a1e82aef59bbe72bd872aea7c6358899bd9d5838b49fa2fc0999c52bdb53b6cb54c5069819b85b20",
"sha256": "da5ccbc9c63c06321402122699b5b0c8b36dd444b379f138288c1de1f3c9ca93",
"sha1": "9bfe28ce28e1faecdd04dfe568d575acb2cec415",
"md5": "cedf7c50fe0e207ef9d6cfc8905d1fcb"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-neoforge",
"version": "1.0.4"
}
]
},
{
"name": "neoforgeRuntimeElements",
"attributes": {
"io.github.mcgradleconventions.loader": "neoforge",
"org.gradle.category": "library",
"org.gradle.dependency.bundling": "external",
"org.gradle.jvm.version": 25,
"org.gradle.libraryelements": "jar",
"org.gradle.usage": "java-runtime"
},
"dependencies": [
{
"group": "net.fabricmc",
"module": "mapping-io",
"version": {
"requires": "0.8.0"
}
}
],
"files": [
{
"name": "testmod-1.0.4-neoforge.jar",
"url": "testmod-1.0.4-neoforge.jar",
"size": 1943,
"sha512": "6d99e94ea072c676054662777708eaa5c4375ae0f8c2f6e3a1e82aef59bbe72bd872aea7c6358899bd9d5838b49fa2fc0999c52bdb53b6cb54c5069819b85b20",
"sha256": "da5ccbc9c63c06321402122699b5b0c8b36dd444b379f138288c1de1f3c9ca93",
"sha1": "9bfe28ce28e1faecdd04dfe568d575acb2cec415",
"md5": "cedf7c50fe0e207ef9d6cfc8905d1fcb"
}
],
"capabilities": [
{
"group": "dev.isxander",
"name": "testmod",
"version": "1.0.4"
},
{
"group": "dev.isxander",
"name": "testmod-neoforge",
"version": "1.0.4"
}
]
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment