plugins { id "java-library" id "eclipse" id "maven-publish" id "fabric-loom" version "1.10.1" apply false id "com.diffplug.spotless" version "7.0.2" id "me.modmuss50.remotesign" version "0.5.0" apply false id "me.modmuss50.mod-publish-plugin" version "0.4.5" } def branchProvider = providers.of(GitBranchValueSource.class) {} version = project.version + "+" + (providers.environmentVariable("CI").present ? branchProvider.get() : "local") logger.lifecycle("Building Fabric: " + version) def metaProjects = [ 'deprecated', 'fabric-api-bom', 'fabric-api-catalog' ] def debugArgs = [ "-enableassertions", "-Dmixin.debug.verify=true", //"-Dmixin.debug.strict=true", "-Dmixin.debug.countInjections=true", ] import groovy.json.JsonSlurper import org.apache.commons.codec.digest.DigestUtils import net.fabricmc.fabric.impl.build.CommitHashValueSource import net.fabricmc.fabric.impl.build.GitBranchValueSource import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse def getSubprojectVersion(Project project) { // Get the version from the gradle.properties file def version = project.properties["${project.name}-version"] if (!version) { throw new NullPointerException("Could not find version for " + project.name) } if (!project.providers.environmentVariable("CI").present) { return version + "+local" } def hashProvider = project.providers.of(CommitHashValueSource.class) { parameters.directory = project.name } return version + "+" + hashProvider.get().substring(0, 8) + DigestUtils.sha256Hex(project.rootProject.minecraft_version).substring(0, 2) } def moduleDependencies(project, List<String> depNames) { def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it", configuration: 'namedElements') } def clientOutputs = depNames.iterator().collect { findProject(":$it").sourceSets.client.output } project.dependencies { deps.each { api it } clientOutputs.each { clientImplementation it } } def depNodes = deps.collect { [ groupId: it.group, artifactId: it.name, version: getSubprojectVersion(project.project(":" + it.name)), scope: "compile" ] } // As we manually handle the maven artifacts, we need to also manually specify the deps. project.publishing { publications { mavenJava(MavenPublication) { pom.withXml { def depsNode = asNode().appendNode("dependencies") for (def depNode in depNodes) { def node = depsNode.appendNode("dependency") for (def entry in depNode) { node.appendNode(entry.key, entry.value) } } } } } } } def testDependencies(project, List<String> depNames) { def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it", configuration: 'namedElements') } def clientOutputs = depNames.iterator().collect { findProject(":$it").sourceSets.client.output } project.dependencies { deps.each { testmodImplementation it } clientOutputs.each { testmodClientImplementation it } } } allprojects { group = "net.fabricmc.fabric-api" apply plugin: "maven-publish" apply plugin: "me.modmuss50.remotesign" tasks.withType(GenerateModuleMetadata) { enabled = false } remoteSign { requestUrl = providers.environmentVariable("SIGNING_SERVER") pgpAuthKey = providers.environmentVariable("SIGNING_PGP_KEY") jarAuthKey = providers.environmentVariable("SIGNING_JAR_KEY") useDummyForTesting = !providers.environmentVariable("SIGNING_SERVER").present afterEvaluate { // PGP sign all maven publications. sign publishing.publications.mavenJava } } publishing { setupRepositories(repositories) } if (metaProjects.contains(it.name)) { return } apply plugin: "java-library" apply plugin: "checkstyle" apply plugin: "fabric-loom" apply plugin: "com.diffplug.spotless" tasks.withType(JavaCompile).configureEach { it.options.release = 21 } java { // Must be added before the split source sets are setup. withSourcesJar() } loom { splitEnvironmentSourceSets() mixin { useLegacyMixinAp = false } } sourceSets { testmod { compileClasspath += main.compileClasspath runtimeClasspath += main.runtimeClasspath } testmodClient { compileClasspath += main.compileClasspath runtimeClasspath += main.runtimeClasspath compileClasspath += client.compileClasspath runtimeClasspath += client.runtimeClasspath compileClasspath += testmod.compileClasspath runtimeClasspath += testmod.runtimeClasspath } test { compileClasspath += testmodClient.compileClasspath runtimeClasspath += testmodClient.runtimeClasspath } } loom { runtimeOnlyLog4j = true runs { testmodClient { client() ideConfigGenerated project.rootProject == project name = "Testmod Client" source sourceSets.testmodClient } testmodServer { server() ideConfigGenerated project.rootProject == project name = "Testmod Server" source sourceSets.testmod } } } loom.runs.configureEach { vmArgs(debugArgs) } allprojects.each { p -> if (metaProjects.contains(p.name)) { return } loom.mods.register(p.name) { sourceSet p.sourceSets.main sourceSet p.sourceSets.client } loom.mods.register(p.name + "-testmod") { sourceSet p.sourceSets.testmod sourceSet p.sourceSets.testmodClient } } dependencies { minecraft "com.mojang:minecraft:$rootProject.minecraft_version" mappings "net.fabricmc:yarn:${rootProject.minecraft_version}${project.yarn_version}:v2" modApi "net.fabricmc:fabric-loader:${project.loader_version}" testmodImplementation sourceSets.main.output testmodClientImplementation sourceSets.main.output testmodClientImplementation sourceSets.client.output testmodClientImplementation sourceSets.testmod.output testImplementation "net.fabricmc:fabric-loader-junit:${project.loader_version}" testImplementation sourceSets.testmodClient.output testImplementation 'org.mockito:mockito-core:5.13.0' } test { useJUnitPlatform() } tasks.withType(ProcessResources).configureEach { inputs.property "version", project.version filesMatching("fabric.mod.json") { expand "version": inputs.properties.version } } spotless { lineEndings = com.diffplug.spotless.LineEnding.UNIX java { licenseHeaderFile(rootProject.file("HEADER")) removeUnusedImports() importOrder('java', 'javax', '', 'net.minecraft', 'net.fabricmc') leadingSpacesToTabs() trimTrailingWhitespace() } // Sort the en_us translation files // The other languages are handled by Crowdin json { target 'src/**/lang/en_us.json' targetExclude 'src/**/generated/**' gson().indentWithSpaces(2).sortByKeys() } } checkstyle { configFile = rootProject.file("checkstyle.xml") toolVersion = "10.20.2" } tasks.withType(AbstractArchiveTask).configureEach { preserveFileTimestamps = false reproducibleFileOrder = true } remoteSign { sign remapJar } // Run this task after updating minecraft to regenerate any required resources tasks.register('generateResources') { group = "fabric" } tasks.register('testmodJar', Jar) { from sourceSets.testmod.output from sourceSets.testmodClient.output destinationDirectory = new File(project.buildDir, "devlibs") archiveClassifier = "testmod" } String archivesName = project.base.archivesName.get() [jar, sourcesJar].each { it.from(rootProject.file("LICENSE")) { rename { "${it}-${archivesName}"} } } if (file("src/client").exists() && !file("src/main").exists()) { tasks.named("remapJar", net.fabricmc.loom.task.AbstractRemapJarTask) { additionalClientOnlyEntries.add("LICENSE-${archivesName}".toString()) } remapSourcesJar { additionalClientOnlyEntries.add("LICENSE-${archivesName}".toString()) } } tasks.register('remapTestmodJar', net.fabricmc.loom.task.RemapJarTask) { dependsOn testmodJar input = testmodJar.archiveFile archiveClassifier = "testmod" addNestedDependencies = false includesClientOnlyClasses = true clientOnlySourceSetName = sourceSets.testmodClient.name classpath.from(sourceSets.testmodClient.compileClasspath) } build.dependsOn remapTestmodJar tasks.register('validateMixinNames', net.fabricmc.loom.task.ValidateMixinNameTask) { source(sourceSets.main.output) source(sourceSets.client.output) source(sourceSets.testmod.output) } // Apply to each valid subproject. apply from: rootProject.file('gradle/package-info.gradle') apply from: rootProject.file('gradle/validate-annotations.gradle') } remapTestmodJar { def testModJarTasks = [] subprojects { if (metaProjects.contains(it.name) || !(it.file("src/testmod").exists() || it.file("src/testmodClient").exists())) { return } testModJarTasks += it.tasks.remapTestmodJar } nestedJars.setFrom(testModJarTasks) addNestedDependencies = true clientOnlySourceSetName = sourceSets.testmodClient.name } // Apply auxiliary buildscripts to submodules // This must be done after all plugins are applied to subprojects apply from: "gradle/module-validation.gradle" apply from: "gradle/module-versioning.gradle" loom { accessWidenerPath = file("gradle/javadoc.accesswidener") } javadoc { options { source = "21" encoding = "UTF-8" charSet = "UTF-8" memberLevel = JavadocMemberLevel.PACKAGE links( "https://maven.fabricmc.net/docs/yarn-${rootProject.minecraft_version}${project.yarn_version}/" ) // Disable the crazy super-strict doclint tool in Java 8 addStringOption("Xdoclint:none", "-quiet") tags( 'apiNote:a:API Note:', 'implSpec:a:Implementation Requirements:', 'implNote:a:Implementation Note:' ) } allprojects.each { if (metaProjects.contains(it.name)) { return } source(it.sourceSets.main.allJava) source(it.sourceSets.client.allJava) } classpath = files(sourceSets.main.compileClasspath, sourceSets.client.compileClasspath) include("**/api/**") failOnError = true } tasks.register('javadocJar', Jar) { dependsOn javadoc from javadoc.destinationDir //Set as `fatjavadoc` to prevent an ide form trying to use this javadoc, over using the modules javadoc archiveClassifier = "fatjavadoc" } build.dependsOn javadocJar loom { runs { gametest { inherit testmodServer name "Game Test" // Enable the gametest runner vmArg "-Dfabric-api.gametest" vmArg "-Dfabric-api.gametest.report-file=${project.layout.buildDirectory.file("junit.xml").get().getAsFile()}" runDir "build/gametest" } autoTestServer { inherit testmodServer name "Auto Test Server" vmArg "-Dfabric.autoTest" } clientGametest { inherit testmodClient name "Client Game Test" vmArg "-Dfabric.client.gametest" vmArg "-Dfabric-tag-conventions-v2.missingTagTranslationWarning=fail" vmArg "-Dfabric-tag-conventions-v1.legacyTagWarning=fail" } } } runGametest { outputs.file project.layout.buildDirectory.file("junit.xml") } test.dependsOn runGametest configurations { productionMods { transitive = false } } dependencies { productionMods project(':fabric-client-gametest-api-v1') } def productionMods = project.files(configurations.productionMods, remapJar.archiveFile, remapTestmodJar.archiveFile) tasks.register('runProductionClientGametest', net.fabricmc.loom.task.prod.ClientProductionRunTask) { mods.setFrom productionMods jvmArgs.addAll([ "-Dfabric.client.gametest", "-Dfabric-tag-conventions-v2.missingTagTranslationWarning=fail", "-Dfabric-tag-conventions-v1.legacyTagWarning=fail" ]) jvmArgs.addAll(debugArgs) if (providers.environmentVariable("ENABLE_TRACY").present) { tracy { tracyCapture = file("tracy-capture") output = file("profile.tracy") } } } tasks.register('runProductionAutoTestServer', net.fabricmc.loom.task.prod.ServerProductionRunTask) { mods.setFrom productionMods jvmArgs.add("-Dfabric.autoTest") jvmArgs.addAll(debugArgs) programArgs.add("nogui") } // Format all the gradle files spotless { groovyGradle { target 'src/**/*.gradle', '*.gradle', 'gradle/*.gradle' greclipse() } } def addPomMetadataInformation(Project project, MavenPom pom) { def modJsonFile = project.file("src/main/resources/fabric.mod.json") if (!modJsonFile.exists()) { modJsonFile = project.file("src/client/resources/fabric.mod.json") } def modJson = new JsonSlurper().parse(modJsonFile) pom.name = modJson.name pom.url = "https://github.com/FabricMC/fabric/tree/HEAD/${project.rootDir.relativePath(project.projectDir)}" pom.description = modJson.description pom.licenses { license { name = "Apache-2.0" url = "https://github.com/FabricMC/fabric/blob/HEAD/LICENSE" } } pom.developers { developer { name = "FabricMC" url = "https://fabricmc.net/" } } pom.scm { connection = "scm:git:https://github.com/FabricMC/fabric.git" url = "https://github.com/FabricMC/fabric" developerConnection = "scm:git:git@github.com:FabricMC/fabric.git" } pom.issueManagement { system = "GitHub" url = "https://github.com/FabricMC/fabric/issues" } } subprojects { if (metaProjects.contains(it.name)) { return } base { archivesName = project.name } dependencies { testmodImplementation sourceSets.main.output // Make all modules depend on the gametest api (and thus res loader) to try and promote its usage. if (project.name != "fabric-gametest-api-v1") { testmodImplementation project(path: ':fabric-gametest-api-v1', configuration: 'namedElements') testmodClientImplementation project(":fabric-gametest-api-v1").sourceSets.client.output testmodImplementation project(path: ':fabric-resource-loader-v0', configuration: 'namedElements') testmodClientImplementation project(":fabric-resource-loader-v0").sourceSets.client.output } // Make all testmods run with registry-sync-v0 as it is required to register new objects. if (project.name != "fabric-registry-sync-v0") { testmodRuntimeOnly project(path: ':fabric-registry-sync-v0', configuration: 'namedElements') testmodClientImplementation project(":fabric-registry-sync-v0").sourceSets.client.output } } publishing { publications { mavenJava(MavenPublication) { pom { addPomMetadataInformation(project, pom) } artifact(signRemapJar.output) { builtBy(signRemapJar) } artifact(remapSourcesJar) { builtBy remapSourcesJar } } } } def projectName = it.name def projectVersion = getSubprojectVersion(it) // Skip publishing if the artifact already exists on the maven server tasks.withType(PublishToMavenRepository).configureEach { onlyIf { if (!providers.environmentVariable("MAVEN_URL").present) { // Always try to publish if the maven url is not set (e.g locally) return true } def artifactPath = "https://maven.fabricmc.net/net/fabricmc/fabric-api/${projectName}/${projectVersion}/${projectName}-${projectVersion}.pom" boolean exists = HttpClient.newHttpClient().withCloseable { client -> def request = HttpRequest.newBuilder() .uri(URI.create(artifactPath)) .method("HEAD", HttpRequest.BodyPublishers.noBody()) .build() def response = client.send(request, HttpResponse.BodyHandlers.discarding()) response.statusCode() == 200 } if (exists) { logger.lifecycle("${projectName}-${projectVersion}.pom has already been published") } else { logger.lifecycle("${projectName}-${projectVersion}.pom does not exist, publishing") } return !exists } } // We manually handle the pom generation loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava) javadoc.enabled = false } publishing { publications { mavenJava(MavenPublication) { artifact(signRemapJar.output) { builtBy(signRemapJar) } artifact(sourcesJar) { builtBy remapSourcesJar } artifact javadocJar artifact remapTestmodJar pom { addPomMetadataInformation(rootProject, pom) } List<Map<String, String>> dependencies = [] subprojects.each { // The maven BOM containing all of the deprecated modules is added manually below. if (it.path.startsWith(":deprecated") || metaProjects.contains(it.name)) { return } dependencies.add([ 'groupId': it.group, 'artifactId': it.name, 'version': getSubprojectVersion(it), 'scope': 'compile' ]) } def thisGroup = group def thisVersion = version pom.withXml { def depsNode = asNode().appendNode("dependencies") for (dep in dependencies) { def depNode = depsNode.appendNode("dependency") depNode.appendNode("groupId", dep['groupId']) depNode.appendNode("artifactId", dep['artifactId']) depNode.appendNode("version", dep['version']) depNode.appendNode("scope", dep['scope']) } // Depend on the deprecated BOM to allow opting out of deprecated modules. def depNode = depsNode.appendNode("dependency") depNode.appendNode("groupId", thisGroup) depNode.appendNode("artifactId", "fabric-api-deprecated") depNode.appendNode("version", thisVersion) depNode.appendNode("scope", "compile") } } } } // Required until the deprecation is removed. Fabric API's main jar that is published to maven does not contain sub modules. loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava) void setupRepositories(RepositoryHandler repositories) { //repositories.mavenLocal() // uncomment for testing if (providers.environmentVariable("MAVEN_URL").present) { repositories.maven { url = providers.environmentVariable("MAVEN_URL") credentials { username = providers.environmentVariable("MAVEN_USERNAME").get() password = providers.environmentVariable("MAVEN_PASSWORD").get() } } } } subprojects.each { if (metaProjects.contains(it.name)) { return } remapJar.dependsOn("${it.path}:remapJar") } // These modules are not included in the fat jar, maven will resolve them via the pom. def devOnlyModules = [ "fabric-client-gametest-api-v1", "fabric-gametest-api-v1", ] dependencies { afterEvaluate { subprojects.each { if (metaProjects.contains(it.name)) { return } api project(path: "${it.path}", configuration: "namedElements") clientImplementation project("${it.path}:").sourceSets.client.output testmodImplementation project("${it.path}:").sourceSets.testmod.output testmodClientImplementation project("${it.path}:").sourceSets.testmodClient.output } } } configurations { nestedJars { transitive = false } } dependencies { subprojects.each { if (it.name in devOnlyModules || metaProjects.contains(it.name)) { return } nestedJars project("${it.path}") } } remapJar { nestedJars.from configurations.nestedJars } // Attempt to create a single jar with all files from all nested jars, this will fail if there are duplicate files. tasks.register("checkNoDuplicateFiles", Zip) { inputs.files configurations.nestedJars destinationDirectory = layout.buildDirectory.dir("test") from { configurations.nestedJars.files.collect { zipTree(it) } } // We expect these files to be duplicated, so exclude them. exclude 'META-INF/**' exclude 'fabric.mod.json' } check.dependsOn "checkNoDuplicateFiles" publishMods { file = signRemapJar.output changelog = providers.environmentVariable("CHANGELOG").getOrElse("No changelog provided") type = project.prerelease == "true" ? BETA : STABLE displayName = "[${project.minecraft_version}] Fabric API $project.version" modLoaders.add("fabric") dryRun = providers.environmentVariable("CURSEFORGE_API_KEY").getOrNull() == null curseforge { accessToken = providers.environmentVariable("CURSEFORGE_API_KEY") projectId = "306612" minecraftVersions.add(project.curseforge_minecraft_version) } modrinth { accessToken = providers.environmentVariable("MODRINTH_TOKEN") projectId = "P7dR8mSH" minecraftVersions.add(project.minecraft_version) } github { accessToken = providers.environmentVariable("GITHUB_TOKEN") repository = providers.environmentVariable("GITHUB_REPOSITORY").getOrElse("FabricMC/dryrun") commitish = providers.environmentVariable("GITHUB_REF_NAME").getOrElse("dryrun") } } assemble.dependsOn signRemapJar import java.util.stream.Collectors // A task to ensure that the version being released has not already been released. tasks.register('checkVersion') { doFirst { def xml = new URL("https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml").text def metadata = new XmlSlurper().parseText(xml) def versions = metadata.versioning.versions.version*.text(); if (versions.contains(version)) { throw new RuntimeException("${version} has already been released!") } } } tasks.publishMods.dependsOn checkVersion publish.mustRunAfter checkVersion