diff --git a/build.gradle b/build.gradle index 4047642e2fe..b3a59972c50 100644 --- a/build.gradle +++ b/build.gradle @@ -1,721 +1,6 @@ plugins { - id "java-library" - id "eclipse" - id "maven-publish" - id "net.fabricmc.fabric-loom" version "1.16.2" apply false - id "com.diffplug.spotless" version "8.5.1" - id "me.modmuss50.remotesign" version "0.5.0" apply false - id "me.modmuss50.mod-publish-plugin" version "2.0.0-beta.1" + alias(libs.plugins.spotless) apply false + alias(libs.plugins.remote.sign) apply false + alias(libs.plugins.mod.publish) apply false + id "fabric-api.root" } - -def branchProvider = providers.of(GitBranchValueSource.class) {} -version = project.version + "+" + (providers.environmentVariable("CI").present ? branchProvider.get().replace("/", "_") : "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", - "-XX:+UseZGC", - "-XX:+UseCompactObjectHeaders", - "-XX:+AlwaysPreTouch", - "-XX:+UseStringDeduplication" -] - -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 depNames) { - def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it") } - 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 depNames) { - def deps = depNames.iterator().collect { project.dependencies.project(path: ":$it") } - 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).configureEach { - 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: "net.fabricmc.fabric-loom" - apply plugin: "com.diffplug.spotless" - - tasks.withType(JavaCompile).configureEach { - it.options.release = 25 - } - - java { - // Must be added before the split source sets are setup. - withSourcesJar() - } - - loom { - splitEnvironmentSourceSets() - } - - 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" - api "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.23.0' - } - - test { - useJUnitPlatform() - jvmArgs(debugArgs) - } - - 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 = "13.3.0" - } - - tasks.withType(AbstractArchiveTask).configureEach { - preserveFileTimestamps = false - reproducibleFileOrder = true - } - - remoteSign { - sign jar - } - - // 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 - archiveClassifier = "testmod" - } - - build.dependsOn testmodJar - - String archivesName = project.base.archivesName.get() - [jar, sourcesJar].each { - it.from(rootProject.file("LICENSE")) { - rename { "${it}-${archivesName}"} - } - } - - tasks.register('enigma', net.fabricmc.loom.task.tool.ModEnigmaTask) { - def clientOnly = file('src/client').exists() && !file('src/main').exists() - def dir = clientOnly ? file('src/client/resources') : file('src/main/resources') - mappingFile = new File(dir, project.name + '.mapping') - } - - tasks.register('validateMixinNames', net.fabricmc.loom.task.ValidateMixinNameTask) { - source(sourceSets.main.output) - source(sourceSets.client.output) - source(sourceSets.testmod.output) - outputs.upToDateWhen { true } // Task has no outputs - } - check.dependsOn "validateMixinNames" - - // Apply to each valid subproject. - apply from: rootProject.file('gradle/package-info.gradle') - apply from: rootProject.file('gradle/validate-annotations.gradle') -} - -def nestedTestModJars = project.files() - -subprojects { - if (metaProjects.contains(it.name) || !(it.file("src/testmod").exists() || it.file("src/testmodClient").exists())) { - return - } - - nestedTestModJars.from(it.tasks.named("testmodJar")) -} - -loom.nestJars(tasks.named("testmodJar"), nestedTestModJars) - -// 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.classtweaker") -} - -javadoc { - options { - source = "25" - encoding = "UTF-8" - charSet = "UTF-8" - memberLevel = JavadocMemberLevel.PACKAGE - addStringOption("Xdoclint:all,-missing", "-quiet") - addBooleanOption("-syntax-highlight", true) - - 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") -} - -// 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') - testmodClientImplementation this.project(":fabric-gametest-api-v1").sourceSets.client.output - testmodImplementation project(path: ':fabric-resource-loader-v1') - testmodClientImplementation this.project(":fabric-resource-loader-v1").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') - testmodClientImplementation this.project(":fabric-registry-sync-v0").sourceSets.client.output - } - } - - publishing { - publications { - mavenJava(MavenPublication) { - pom { - addPomMetadataInformation(project, pom) - } - artifact(tasks.jar.archiveFile) { - builtBy(tasks.jar) - } - - artifact(tasks.sourcesJar) { - builtBy tasks.sourcesJar - } - } - } - } - - 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(signJar.output) { - builtBy(signJar) - } - - artifact(sourcesJar) { - builtBy sourcesJar - } - - artifact javadocJar - artifact testmodJar - - pom { - addPomMetadataInformation(rootProject, pom) - } - - List> 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() - } - } - } -} - -// 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 { - subprojects.each { - if (metaProjects.contains(it.name)) { - return - } - - api project(path: "${it.path}") - clientImplementation this.project("${it.path}:").sourceSets.client.output - - testmodImplementation this.project("${it.path}:").sourceSets.testmod.output - testmodClientImplementation this.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}") - } -} - -loom.nestJars(tasks.named("jar"), 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 = signJar.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" - project.curseforge_minecraft_versions.split(",").each { minecraftVersions.add(it.trim()) } - } - - modrinth { - accessToken = providers.environmentVariable("MODRINTH_TOKEN") - projectId = "P7dR8mSH" - minecraftVersions.add(project.minecraft_version) - if (project.modrinth_extra_minecraft_versions) { - project.modrinth_extra_minecraft_versions.split(",").each { minecraftVersions.add(it.trim()) } - } - } - - github { - accessToken = providers.environmentVariable("GITHUB_TOKEN") - repository = providers.environmentVariable("GITHUB_REPOSITORY").getOrElse("FabricMC/dryrun") - commitish = providers.environmentVariable("GITHUB_REF_NAME").getOrElse("dryrun") - } -} - -assemble.dependsOn signJar - -// Copy the final jar into a directory of its own so we can easily upload it without zipping during github actions builds. -tasks.register('outJar', Copy) { - from signJar.output - into layout.buildDirectory.dir("out") -} - -// 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 groovy.xml.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 diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index aabaea66a72..8b36645ebc4 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,25 +1,40 @@ plugins { + id 'groovy-gradle-plugin' id 'checkstyle' - id "com.diffplug.spotless" version "6.20.0" + alias(libs.plugins.spotless) } repositories { + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() mavenCentral() } +dependencies { + implementation libs.fabric.loom +} + checkstyle { configFile = file("../checkstyle.xml") - toolVersion = "10.20.2" + toolVersion = libs.versions.checkstyle.get() } spotless { lineEndings = com.diffplug.spotless.LineEnding.UNIX java { + target("src/main/java/**/*.java") licenseHeaderFile(file("../HEADER")) removeUnusedImports() importOrder('java', 'javax', '', 'net.minecraft', 'net.fabricmc') - indentWithTabs() + leadingSpacesToTabs() trimTrailingWhitespace() } } + +tasks.withType(Checkstyle).configureEach { + source = fileTree("src/main/java") +} diff --git a/buildSrc/settings.gradle b/buildSrc/settings.gradle new file mode 100644 index 00000000000..0aba7326a42 --- /dev/null +++ b/buildSrc/settings.gradle @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + libs { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.base-publishing.gradle b/buildSrc/src/main/groovy/fabric-api.base-publishing.gradle new file mode 100644 index 00000000000..6fbd10c8c69 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.base-publishing.gradle @@ -0,0 +1,27 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +plugins { + id "maven-publish" + id "me.modmuss50.remotesign" +} + +group = "net.fabricmc.fabric-api" + +tasks.withType(GenerateModuleMetadata).configureEach { + 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 { + sign publishing.publications.mavenJava + } +} + +publishing { + FabricApiBuildUtils.setupRepositories(project, repositories) +} diff --git a/buildSrc/src/main/groovy/fabric-api.deprecated-bom.gradle b/buildSrc/src/main/groovy/fabric-api.deprecated-bom.gradle new file mode 100644 index 00000000000..30776087baa --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.deprecated-bom.gradle @@ -0,0 +1,38 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils +import org.gradle.api.publish.maven.MavenPublication + +plugins { + id "fabric-api.base-publishing" +} + +version = rootProject.version + +def dependencies = rootProject.allprojects.findAll { + it.name != "deprecated" && it.path.startsWith(":deprecated") +}.collect { + [ + 'groupId': it.group.toString(), + 'artifactId': it.name, + 'version': FabricApiBuildUtils.moduleVersion(it), + 'scope': 'compile' + ] +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId = 'fabric-api-deprecated' + + 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']) + } + } + } + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.java.gradle b/buildSrc/src/main/groovy/fabric-api.java.gradle new file mode 100644 index 00000000000..ca146cf2515 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.java.gradle @@ -0,0 +1,16 @@ +plugins { + id "java-library" +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 25 +} + +java { + withSourcesJar() +} + +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true +} diff --git a/buildSrc/src/main/groovy/fabric-api.lint.gradle b/buildSrc/src/main/groovy/fabric-api.lint.gradle new file mode 100644 index 00000000000..2fd58410f58 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.lint.gradle @@ -0,0 +1,38 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +plugins { + id "checkstyle" + id "com.diffplug.spotless" +} + +checkstyle { + configFile = rootProject.file("checkstyle.xml") + toolVersion = FabricApiBuildUtils.version(project, "checkstyle") +} + +spotless { + lineEndings = 'UNIX' + + java { + licenseHeaderFile(rootProject.file("HEADER")) + removeUnusedImports() + importOrder('java', 'javax', '', 'net.minecraft', 'net.fabricmc') + leadingSpacesToTabs() + trimTrailingWhitespace() + } + + json { + target 'src/**/lang/en_us.json' + targetExclude 'src/**/generated/**' + gson().indentWithSpaces(2).sortByKeys() + } +} + +if (project == rootProject) { + spotless { + groovyGradle { + target 'src/**/*.gradle', '*.gradle', 'gradle/*.gradle' + greclipse() + } + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.loom.gradle b/buildSrc/src/main/groovy/fabric-api.loom.gradle new file mode 100644 index 00000000000..b35bc309ed6 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.loom.gradle @@ -0,0 +1,38 @@ +import net.fabricmc.loom.task.tool.ModEnigmaTask +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +plugins { + id "net.fabricmc.fabric-loom" +} + +loom { + splitEnvironmentSourceSets() + runtimeOnlyLog4j = true + runs.configureEach { + preferGradleTask = true + } +} + +dependencies { + minecraft "com.mojang:minecraft:$rootProject.minecraft_version" + api FabricApiBuildUtils.library(project, "fabric-loader") +} + +tasks.withType(ProcessResources).configureEach { + def modVersion = project.version.toString() + inputs.property "version", modVersion + + filesMatching("fabric.mod.json") { + expand "version": modVersion + } +} + +tasks.register('generateResources') { + group = "fabric" +} + +tasks.register('enigma', ModEnigmaTask) { + def clientOnly = file('src/client').exists() && !file('src/main').exists() + def dir = clientOnly ? file('src/client/resources') : file('src/main/resources') + mappingFile = new File(dir, project.name + '.mapping') +} diff --git a/buildSrc/src/main/groovy/fabric-api.maven-bom.gradle b/buildSrc/src/main/groovy/fabric-api.maven-bom.gradle new file mode 100644 index 00000000000..8c980f85339 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.maven-bom.gradle @@ -0,0 +1,30 @@ +import org.gradle.api.publish.maven.MavenPublication + +plugins { + id "java-platform" + id "fabric-api.base-publishing" +} + +version = rootProject.version + +publishing.publications { + register('mavenJava', MavenPublication) { + from(components['javaPlatform']) + } +} + +tasks.withType(GenerateModuleMetadata).configureEach { + enabled = true +} + +dependencies { + constraints { + for (proj in rootProject.allprojects) { + if (proj == project || proj.name == 'fabric-api-catalog') { + continue + } + + api(project(proj.path)) + } + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.mixin-config.gradle b/buildSrc/src/main/groovy/fabric-api.mixin-config.gradle new file mode 100644 index 00000000000..fc0f446e969 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.mixin-config.gradle @@ -0,0 +1,28 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +plugins { + id "fabric-api.module" +} + +sourceSets { + mixinConfig { + compileClasspath += configurations.loaderLibraries + } + client { + compileClasspath += mixinConfig.output + runtimeClasspath += mixinConfig.output + } +} + +configurations { + clientImplementation.extendsFrom mixinConfigImplementation + clientRuntimeOnly.extendsFrom mixinConfigRuntimeOnly +} + +dependencies { + mixinConfigImplementation FabricApiBuildUtils.library(project, "fabric-loader") +} + +tasks.named("jar", Jar) { + from sourceSets.mixinConfig.output +} diff --git a/buildSrc/src/main/groovy/fabric-api.module-versioning.gradle b/buildSrc/src/main/groovy/fabric-api.module-versioning.gradle new file mode 100644 index 00000000000..1b52e6cc410 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.module-versioning.gradle @@ -0,0 +1,3 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +version = FabricApiBuildUtils.moduleVersion(project) diff --git a/buildSrc/src/main/groovy/fabric-api.module.gradle b/buildSrc/src/main/groovy/fabric-api.module.gradle new file mode 100644 index 00000000000..d1b9086a153 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.module.gradle @@ -0,0 +1,13 @@ +import net.fabricmc.fabric.impl.build.FabricApiModuleExtension + +plugins { + id "fabric-api.module-versioning" + id "fabric-api.java" + id "fabric-api.lint" + id "fabric-api.loom" + id "fabric-api.testing" + id "fabric-api.validation" + id "fabric-api.publishing" +} + +extensions.create("fabricApiModule", FabricApiModuleExtension, project) diff --git a/buildSrc/src/main/groovy/fabric-api.publishing.gradle b/buildSrc/src/main/groovy/fabric-api.publishing.gradle new file mode 100644 index 00000000000..10af392625a --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.publishing.gradle @@ -0,0 +1,56 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils +import org.gradle.api.publish.maven.MavenPublication + +plugins { + id "fabric-api.base-publishing" +} + +base { + archivesName = project.name +} + +publishing { + publications { + mavenJava(MavenPublication) { + pom { + FabricApiBuildUtils.addPomMetadataInformation(project, pom) + } + artifact(tasks.jar.archiveFile) { + builtBy(tasks.jar) + } + artifact(tasks.sourcesJar) { + builtBy tasks.sourcesJar + } + } + } +} + +remoteSign { + sign jar +} + +String archivesName = project.base.archivesName.get() +[jar, sourcesJar].each { + it.from(rootProject.file("LICENSE")) { + rename { "${it}-${archivesName}" } + } +} + +tasks.withType(PublishToMavenRepository).configureEach { + onlyIf { + def projectName = project.name + def projectVersion = project.version.toString() + def exists = FabricApiBuildUtils.publishedArtifactExists(project, projectName, projectVersion) + + if (exists) { + logger.lifecycle("${projectName}-${projectVersion}.pom has already been published") + } else if (providers.environmentVariable("MAVEN_URL").present) { + logger.lifecycle("${projectName}-${projectVersion}.pom does not exist, publishing") + } + + return !exists + } +} + +loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava) +javadoc.enabled = false diff --git a/buildSrc/src/main/groovy/fabric-api.root-publishing.gradle b/buildSrc/src/main/groovy/fabric-api.root-publishing.gradle new file mode 100644 index 00000000000..109d5f7cb64 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.root-publishing.gradle @@ -0,0 +1,176 @@ +import groovy.xml.XmlSlurper +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.external.javadoc.JavadocMemberLevel + +plugins { + id "fabric-api.base-publishing" + id "me.modmuss50.mod-publish-plugin" +} + +def includedModuleProjects = rootProject.allprojects.findAll { + FabricApiBuildUtils.isFabricModule(it) && it != project +} + +javadoc { + enabled = true + + options { + source = "25" + encoding = "UTF-8" + charSet = "UTF-8" + memberLevel = JavadocMemberLevel.PACKAGE + addStringOption("Xdoclint:all,-missing", "-quiet") + addBooleanOption("-syntax-highlight", true) + + tags( + 'apiNote:a:API Note:', + 'implSpec:a:Implementation Requirements:', + 'implNote:a:Implementation Note:' + ) + } + + include("**/api/**") + failOnError = true +} + +for (def moduleProject : includedModuleProjects) { + moduleProject.plugins.withId("fabric-api.module") { + javadoc.source(moduleProject.sourceSets.main.allJava) + javadoc.source(moduleProject.sourceSets.client.allJava) + } +} + +tasks.register('javadocJar', Jar) { + dependsOn javadoc + from javadoc.destinationDir + archiveClassifier = "fatjavadoc" +} + +build.dependsOn javadocJar + +remoteSign { + sign jar +} + +String archivesName = project.base.archivesName.get() +[jar, sourcesJar].each { + it.from(rootProject.file("LICENSE")) { + rename { "${it}-${archivesName}" } + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifact(signJar.output) { + builtBy(signJar) + } + + artifact(sourcesJar) { + builtBy sourcesJar + } + + artifact javadocJar + artifact testmodJar + + pom { + FabricApiBuildUtils.addPomMetadataInformation(rootProject, pom) + } + } + } +} + +loom.disableDeprecatedPomGeneration(publishing.publications.mavenJava) + +publishMods { + file = signJar.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" + project.curseforge_minecraft_versions.split(",").each { minecraftVersions.add(it.trim()) } + client = true + server = true + } + + modrinth { + accessToken = providers.environmentVariable("MODRINTH_TOKEN") + projectId = "P7dR8mSH" + minecraftVersions.add(project.minecraft_version) + if (project.modrinth_extra_minecraft_versions) { + project.modrinth_extra_minecraft_versions.split(",").each { minecraftVersions.add(it.trim()) } + } + } + + github { + accessToken = providers.environmentVariable("GITHUB_TOKEN") + repository = providers.environmentVariable("GITHUB_REPOSITORY").getOrElse("FabricMC/dryrun") + commitish = providers.environmentVariable("GITHUB_REF_NAME").getOrElse("dryrun") + } +} + +assemble.dependsOn signJar + +tasks.register('outJar', Copy) { + from signJar.output + into layout.buildDirectory.dir("out") +} + +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 + +gradle.projectsEvaluated { + publishing.publications.mavenJava { + def dependencies = [] + + for (def moduleProject : includedModuleProjects) { + if (moduleProject.path.startsWith(":deprecated")) { + continue + } + + dependencies.add([ + 'groupId': moduleProject.group, + 'artifactId': moduleProject.name, + 'version': FabricApiBuildUtils.moduleVersion(moduleProject), + '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']) + } + + def depNode = depsNode.appendNode("dependency") + depNode.appendNode("groupId", thisGroup) + depNode.appendNode("artifactId", "fabric-api-deprecated") + depNode.appendNode("version", thisVersion) + depNode.appendNode("scope", "compile") + } + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.root-testing.gradle b/buildSrc/src/main/groovy/fabric-api.root-testing.gradle new file mode 100644 index 00000000000..96863350d2e --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.root-testing.gradle @@ -0,0 +1,68 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +plugins { + id "fabric-api.testing" +} + +def includedModuleProjects = rootProject.allprojects.findAll { + FabricApiBuildUtils.isFabricModule(it) && it != project +} + +def nestedTestModJars = project.files() + +for (def moduleProject : includedModuleProjects) { + def currentModuleProject = moduleProject + currentModuleProject.afterEvaluate { + if (currentModuleProject.file("src/testmod").exists() || currentModuleProject.file("src/testmodClient").exists()) { + nestedTestModJars.from(currentModuleProject.tasks.named("testmodJar")) + } + + loom.mods.register(currentModuleProject.name) { + sourceSet currentModuleProject.sourceSets.main + sourceSet currentModuleProject.sourceSets.client + } + + loom.mods.register(currentModuleProject.name + "-testmod") { + sourceSet currentModuleProject.sourceSets.testmod + sourceSet currentModuleProject.sourceSets.testmodClient + } + + dependencies { + testmodImplementation currentModuleProject.sourceSets.testmod.output + testmodClientImplementation currentModuleProject.sourceSets.testmodClient.output + } + } +} + +loom { + runs { + gametest { + inherit testmodServer + name "Game Test" + vmArg "-Dfabric-api.gametest" + vmArg "-Dfabric-api.gametest.report-file=${project.layout.buildDirectory.file("junit.xml").get().getAsFile()}" + runDir "build/gametest" + source sourceSets.testmod + } + autoTestServer { + inherit testmodServer + name "Auto Test Server" + vmArg "-Dfabric.autoTest" + source sourceSets.testmod + } + 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" + source sourceSets.testmodClient + } + } +} + +runGametest { + outputs.file project.layout.buildDirectory.file("junit.xml") +} + +loom.nestJars(tasks.named("testmodJar"), nestedTestModJars) diff --git a/buildSrc/src/main/groovy/fabric-api.root-versioning.gradle b/buildSrc/src/main/groovy/fabric-api.root-versioning.gradle new file mode 100644 index 00000000000..2c0bf3579cb --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.root-versioning.gradle @@ -0,0 +1,26 @@ +import net.fabricmc.fabric.impl.build.BumpVersionTask +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +version = FabricApiBuildUtils.rootVersion(project) +logger.lifecycle("Building Fabric: " + version) + +def bumpVersions = tasks.register('bumpVersions', BumpVersionTask) { + gradlePropertiesFile.set(layout.projectDirectory.file("gradle.properties")) +} + +gradle.projectsEvaluated { + def moduleProjects = rootProject.allprojects.findAll { + FabricApiBuildUtils.isFabricModule(it) && it != rootProject + } + + bumpVersions.configure { + moduleNames.set(moduleProjects.collect { it.name }) + moduleVersions.set(moduleProjects.collectEntries { + [(it.name): it.findProperty("${it.name}-version").toString()] + }) + apiDependencies.set(moduleProjects.collectEntries { + def api = it.configurations.findByName("api") + [(it.name): api == null ? "" : api.allDependencies.collect { dependency -> dependency.name }.join(",")] + }) + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.root.gradle b/buildSrc/src/main/groovy/fabric-api.root.gradle new file mode 100644 index 00000000000..62df9762c95 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.root.gradle @@ -0,0 +1,55 @@ +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +plugins { + id "fabric-api.root-versioning" + id "fabric-api.java" + id "fabric-api.lint" + id "fabric-api.loom" + id "fabric-api.validation" + id "fabric-api.root-testing" + id "fabric-api.root-publishing" +} + +tasks.named("validateModules") { + enabled = false +} + +loom { + accessWidenerPath = file("gradle/javadoc.classtweaker") +} + +configurations { + nestedJars { + transitive = false + } +} + +def includedModuleProjects = rootProject.allprojects.findAll { + FabricApiBuildUtils.isFabricModule(it) && it != project +} + +dependencies { + for (def moduleProject : includedModuleProjects) { + api project(path: moduleProject.path) + + if (!FabricApiBuildUtils.DEV_ONLY_MODULES.contains(moduleProject.name)) { + nestedJars project(moduleProject.path) + } + } +} + +loom.nestJars(tasks.named("jar"), configurations.nestedJars) + +tasks.register("checkNoDuplicateFiles", Zip) { + inputs.files configurations.nestedJars + destinationDirectory = layout.buildDirectory.dir("test") + + from { + configurations.nestedJars.files.collect { zipTree(it) } + } + + exclude 'META-INF/**' + exclude 'fabric.mod.json' +} + +check.dependsOn "checkNoDuplicateFiles" diff --git a/buildSrc/src/main/groovy/fabric-api.testing.gradle b/buildSrc/src/main/groovy/fabric-api.testing.gradle new file mode 100644 index 00000000000..9faabbec969 --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.testing.gradle @@ -0,0 +1,102 @@ +import net.fabricmc.loom.task.ValidateMixinNameTask +import net.fabricmc.fabric.impl.build.FabricApiBuildUtils + +def debugArgs = [ + "-enableassertions", + "-Dmixin.debug.verify=true", + "-Dmixin.debug.countInjections=true", + "-XX:+UseZGC", + "-XX:+UseCompactObjectHeaders", + "-XX:+AlwaysPreTouch", + "-XX:+UseStringDeduplication" +] + +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 { + 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) +} + +dependencies { + testmodImplementation sourceSets.main.output + testmodClientImplementation sourceSets.main.output + testmodClientImplementation sourceSets.client.output + testmodClientImplementation sourceSets.testmod.output + + testImplementation FabricApiBuildUtils.library(project, "fabric-loader-junit") + testImplementation sourceSets.testmodClient.output + testImplementation FabricApiBuildUtils.library(project, "mockito-core") +} + +test { + useJUnitPlatform() + jvmArgs(debugArgs) +} + +tasks.register('testmodJar', Jar) { + from sourceSets.testmod.output + from sourceSets.testmodClient.output + archiveClassifier = "testmod" +} + +build.dependsOn testmodJar + +tasks.register('validateMixinNames', ValidateMixinNameTask) { + source(sourceSets.main.output) + source(sourceSets.client.output) + source(sourceSets.testmod.output) + outputs.upToDateWhen { true } +} + +check.dependsOn "validateMixinNames" + +dependencies { + if (project.name != "fabric-gametest-api-v1" && rootProject.findProject(":fabric-gametest-api-v1") != null) { + testmodImplementation project(path: ':fabric-gametest-api-v1') + testmodClientImplementation rootProject.project(":fabric-gametest-api-v1").sourceSets.client.output + testmodImplementation project(path: ':fabric-resource-loader-v1') + testmodClientImplementation rootProject.project(":fabric-resource-loader-v1").sourceSets.client.output + } + + if (project.name != "fabric-registry-sync-v0" && rootProject.findProject(":fabric-registry-sync-v0") != null) { + testmodRuntimeOnly project(path: ':fabric-registry-sync-v0') + testmodClientImplementation rootProject.project(":fabric-registry-sync-v0").sourceSets.client.output + } +} diff --git a/buildSrc/src/main/groovy/fabric-api.validation.gradle b/buildSrc/src/main/groovy/fabric-api.validation.gradle new file mode 100644 index 00000000000..d620d25bc6a --- /dev/null +++ b/buildSrc/src/main/groovy/fabric-api.validation.gradle @@ -0,0 +1,37 @@ +import net.fabricmc.fabric.impl.build.GeneratePackageInfosTask +import net.fabricmc.fabric.impl.build.ValidateAnnotationsTask +import net.fabricmc.fabric.impl.build.ValidateModuleTask + +tasks.register("validateModules", ValidateModuleTask) +tasks.check.dependsOn("validateModules") + +tasks.register('validateAnnotations', ValidateAnnotationsTask) { + group = 'fabric' + description = "Validate annotations used in Fabric API code." + outputs.upToDateWhen { true } + + source file("src/client/java") + source file("src/main/java") + source file("src/testmod/java") + source file("src/testmodClient/java") +} +tasks.check.dependsOn "validateAnnotations" + +for (def sourceSet in [sourceSets.main, sourceSets.client]) { + def sourceSetName = sourceSet.name + def taskName = sourceSet.getTaskName('generate', 'PackageInfos') + def task = tasks.register(taskName, GeneratePackageInfosTask) { + group = 'fabric' + description = "Generates package-info files for $sourceSetName packages." + sourceRoot.set(file("src/$sourceSetName/java")) + header.set(rootProject.file('HEADER')) + outputDir.set(file("src/generated/$sourceSetName")) + } + sourceSet.java.srcDir task + + def cleanTask = tasks.register(sourceSet.getTaskName('clean', 'PackageInfos'), Delete) { + group = 'fabric' + delete file("src/generated/$sourceSetName") + } + clean.dependsOn cleanTask +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/AbstractGenerateClassTweakerTask.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/AbstractGenerateClassTweakerTask.java new file mode 100644 index 00000000000..2bdcec2a1b5 --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/AbstractGenerateClassTweakerTask.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; + +public abstract class AbstractGenerateClassTweakerTask extends DefaultTask { + @InputFiles + @PathSensitive(PathSensitivity.NONE) + public abstract ConfigurableFileCollection getMinecraftJars(); + + @InputFile + @PathSensitive(PathSensitivity.RELATIVE) + public abstract RegularFileProperty getTemplate(); + + @OutputFile + public abstract RegularFileProperty getOutputFile(); + + public AbstractGenerateClassTweakerTask() { + getMinecraftJars().from(getProject().provider(() -> LoomGradleExtension.get(getProject()).getMinecraftJars(MappingsNamespace.OFFICIAL))); + } + + protected void addHeader(List lines, String format) throws IOException { + addHeader(lines, format, false); + } + + protected void addHeader(List lines, String format, boolean preserveTemplateTrailingNewline) throws IOException { + lines.add(format); + lines.add(""); + lines.add("# DO NOT EDIT BY HAND! This file is generated automatically."); + lines.add("# Edit \"template.classtweaker\" instead then run \"gradlew generateClassTweaker\"."); + lines.add(""); + Path template = getTemplate().get().getAsFile().toPath(); + lines.addAll(Files.readAllLines(template, StandardCharsets.UTF_8)); + + if (preserveTemplateTrailingNewline && Files.readString(template, StandardCharsets.UTF_8).endsWith("\n")) { + lines.add(""); + } + } + + protected void writeOutput(List lines) throws IOException { + File outputFile = getOutputFile().get().getAsFile(); + Files.createDirectories(outputFile.toPath().getParent()); + Files.writeString(outputFile.toPath(), String.join("\n", lines) + "\n", StandardCharsets.UTF_8); + } + + protected Map readClasses() throws IOException { + Map classes = new TreeMap<>(); + + for (File input : getMinecraftJars().getFiles()) { + readClasses(input, classes); + } + + return classes; + } + + protected ClassNode readClass(String name) throws IOException { + return readClass(name, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + + protected List readPackageClasses(String packageName) throws IOException { + return getMinecraftJars().getFiles().stream() + .flatMap(input -> { + try { + return readPackageClasses(input, packageName).stream(); + } catch (IOException e) { + throw new RuntimeException("Failed to read package " + packageName + " from " + input, e); + } + }) + .toList(); + } + + private static void readClasses(File input, Map classes) throws IOException { + try (ZipFile zip = new ZipFile(input)) { + for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) { + ZipEntry entry = entries.nextElement(); + + if (!entry.getName().endsWith(".class")) { + continue; + } + + classes.put(entry.getName().replace(".class", ""), readClass(zip, entry.getName(), ClassReader.SKIP_CODE)); + } + } + } + + private ClassNode readClass(String name, int flags) throws IOException { + String path = name + ".class"; + + for (File input : getMinecraftJars().getFiles()) { + try (ZipFile zip = new ZipFile(input)) { + if (zip.getEntry(path) != null) { + return readClass(zip, path, flags); + } + } + } + + throw new IOException("Missing class " + path + " in " + getMinecraftJars().getFiles()); + } + + private static List readPackageClasses(File input, String packageName) throws IOException { + String prefix = packageName + "/"; + + try (ZipFile zip = new ZipFile(input)) { + return zip.stream() + .filter(entry -> !entry.isDirectory()) + .filter(entry -> entry.getName().startsWith(prefix)) + .filter(entry -> entry.getName().endsWith(".class")) + .filter(entry -> entry.getName().indexOf('/', prefix.length()) == -1) + .map(entry -> { + try { + return readClass(zip, entry.getName(), ClassReader.SKIP_CODE); + } catch (IOException e) { + throw new RuntimeException("Failed to read " + entry.getName() + " from " + input, e); + } + }) + .toList(); + } + } + + private static ClassNode readClass(ZipFile zip, String path, int flags) throws IOException { + ZipEntry entry = zip.getEntry(path); + + if (entry == null) { + throw new IOException("Missing class " + path + " in " + zip.getName()); + } + + try (InputStream inputStream = zip.getInputStream(entry)) { + ClassReader reader = new ClassReader(inputStream); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, flags); + return classNode; + } + } +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/BumpVersionTask.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/BumpVersionTask.java new file mode 100644 index 00000000000..6b287b3e267 --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/BumpVersionTask.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Scanner; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +public abstract class BumpVersionTask extends DefaultTask { + @InputFile + @PathSensitive(PathSensitivity.NONE) + public abstract RegularFileProperty getGradlePropertiesFile(); + + @Input + public abstract ListProperty getModuleNames(); + + @Input + public abstract MapProperty getModuleVersions(); + + @Input + public abstract MapProperty getApiDependencies(); + + public BumpVersionTask() { + setGroup("publishing"); + getOutputs().upToDateWhen(task -> false); + } + + @TaskAction + public void runTask() throws IOException { + LinkedHashMap toUpdate = new LinkedHashMap<>(); + readInteractiveUpdates(toUpdate); + + while (true) { + LinkedHashMap temp = new LinkedHashMap<>(); + + for (String projectName : toUpdate.keySet()) { + getApiDependencies().get().forEach((childProjectName, dependencies) -> { + if (containsDependency(dependencies, projectName) && !toUpdate.containsKey(childProjectName)) { + System.out.println("Bumping patch of " + childProjectName + " as it depends on " + projectName); + temp.put(childProjectName, 2); + } + }); + } + + if (temp.isEmpty()) { + break; + } + + toUpdate.putAll(temp); + } + + File gradlePropertiesFile = getGradlePropertiesFile().get().getAsFile(); + String text = java.nio.file.Files.readString(gradlePropertiesFile.toPath(), StandardCharsets.UTF_8); + + for (Map.Entry entry : toUpdate.entrySet()) { + String projectName = entry.getKey(); + int index = entry.getValue(); + String version = getModuleVersions().get().get(projectName); + + if (version == null) { + throw new NullPointerException("Could not find version for " + projectName); + } + + String[] split = version.split("\\."); + split[index] = Integer.toString(Integer.parseInt(split[index]) + 1); + + for (int i = index + 1; i < split.length; i++) { + split[i] = "0"; + } + + String newVersion = String.join(".", split); + System.out.println(projectName + ": " + version + " -> " + newVersion); + text = text.replace(projectName + "-version=" + version, projectName + "-version=" + newVersion); + } + + java.nio.file.Files.writeString(gradlePropertiesFile.toPath(), text, StandardCharsets.UTF_8); + } + + private void readInteractiveUpdates(LinkedHashMap toUpdate) { + Scanner scanner = new Scanner(System.in); + + while (true) { + System.out.println("Enter module name to update, or done to continue"); + String input = scanner.nextLine(); + + if (input.equals("done")) { + break; + } + + if (input.equals("allPatch")) { + getModuleNames().get().forEach(moduleName -> toUpdate.put(moduleName, 2)); + break; + } + + if (!getModuleNames().get().contains(input)) { + System.out.println("Could not find project with name: " + input); + continue; + } + + while (true) { + System.out.println("Bump version for " + input + ":"); + System.out.println("0) Bump Major"); + System.out.println("1) Bump Minor"); + System.out.println("2) Bump Patch"); + + String bump = scanner.nextLine(); + + if (!bump.equals("0") && !bump.equals("1") && !bump.equals("2")) { + System.out.println("Invalid input"); + continue; + } + + toUpdate.put(input, Integer.parseInt(bump)); + break; + } + } + } + + private static boolean containsDependency(String dependencies, String projectName) { + for (String dependency : dependencies.split(",")) { + if (dependency.equals(projectName)) { + return true; + } + } + + return false; + } +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/FabricApiBuildUtils.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/FabricApiBuildUtils.java new file mode 100644 index 00000000000..0e53c10c63a --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/FabricApiBuildUtils.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import groovy.json.JsonSlurper; +import groovy.util.Node; +import org.gradle.api.Project; +import org.gradle.api.artifacts.MinimalExternalModuleDependency; +import org.gradle.api.artifacts.VersionCatalog; +import org.gradle.api.artifacts.VersionCatalogsExtension; +import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.provider.Provider; +import org.gradle.api.publish.maven.MavenPom; + +public final class FabricApiBuildUtils { + public static final Set META_PROJECTS = Set.of( + "deprecated", + "fabric-api-bom", + "fabric-api-catalog" + ); + + public static final List DEV_ONLY_MODULES = List.of( + "fabric-client-gametest-api-v1", + "fabric-gametest-api-v1" + ); + + private FabricApiBuildUtils() { + } + + public static boolean isMetaProject(Project project) { + return META_PROJECTS.contains(project.getName()); + } + + public static boolean isFabricModule(Project project) { + return !isMetaProject(project); + } + + public static String moduleName(String notation) { + return notation.startsWith(":") ? notation.substring(1) : notation; + } + + public static String projectPath(String notation) { + return notation.startsWith(":") ? notation : ":" + notation; + } + + public static String rootVersion(Project project) { + Provider branchProvider = project.getProviders().of(GitBranchValueSource.class, spec -> { }); + String suffix = project.getProviders().environmentVariable("CI").isPresent() + ? branchProvider.get().replace("/", "_") + : "local"; + return project.findProperty("version") + "+" + suffix; + } + + public static String moduleVersion(Project project) { + Object version = project.findProperty(project.getName() + "-version"); + + if (version == null) { + throw new NullPointerException("Could not find version for " + project.getName()); + } + + if (!project.getProviders().environmentVariable("CI").isPresent()) { + return version + "+local"; + } + + Provider hashProvider = project.getProviders().of(CommitHashValueSource.class, spec -> { + spec.getParameters().getDirectory().set(project.getName()); + }); + + return version + "+" + hashProvider.get().substring(0, 8) + sha256Hex(project.getRootProject().property("minecraft_version").toString()).substring(0, 2); + } + + public static String version(Project project, String alias) { + return libs(project).findVersion(alias) + .orElseThrow(() -> new IllegalStateException("Missing version catalog entry: " + alias)) + .getRequiredVersion(); + } + + public static Provider library(Project project, String alias) { + return libs(project).findLibrary(alias) + .orElseThrow(() -> new IllegalStateException("Missing version catalog entry: " + alias)); + } + + private static VersionCatalog libs(Project project) { + return project.getExtensions().getByType(VersionCatalogsExtension.class).named("libs"); + } + + public static void setupRepositories(Project project, RepositoryHandler repositories) { + if (project.getProviders().environmentVariable("MAVEN_URL").isPresent()) { + repositories.maven(repository -> { + repository.setUrl(project.getProviders().environmentVariable("MAVEN_URL")); + repository.credentials(credentials -> { + credentials.setUsername(project.getProviders().environmentVariable("MAVEN_USERNAME").get()); + credentials.setPassword(project.getProviders().environmentVariable("MAVEN_PASSWORD").get()); + }); + }); + } + } + + public static boolean publishedArtifactExists(Project project, String projectName, String projectVersion) throws IOException, InterruptedException { + if (!project.getProviders().environmentVariable("MAVEN_URL").isPresent()) { + return false; + } + + String artifactPath = "https://maven.fabricmc.net/net/fabricmc/fabric-api/%s/%s/%s-%s.pom".formatted(projectName, projectVersion, projectName, projectVersion); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(artifactPath)) + .method("HEAD", HttpRequest.BodyPublishers.noBody()) + .build(); + + try (HttpClient client = HttpClient.newHttpClient()) { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.discarding()); + return response.statusCode() == 200; + } + } + + public static void addPomMetadataInformation(Project project, MavenPom pom) { + File modJsonFile = project.file("src/main/resources/fabric.mod.json"); + + if (!modJsonFile.exists()) { + modJsonFile = project.file("src/client/resources/fabric.mod.json"); + } + + @SuppressWarnings("unchecked") + Map modJson = (Map) new JsonSlurper().parse(modJsonFile); + pom.getName().set(modJson.get("name").toString()); + pom.getUrl().set("https://github.com/FabricMC/fabric/tree/HEAD/" + project.getRootDir().toPath().relativize(project.getProjectDir().toPath())); + pom.getDescription().set(modJson.get("description").toString()); + pom.licenses(licenses -> licenses.license(license -> { + license.getName().set("Apache-2.0"); + license.getUrl().set("https://github.com/FabricMC/fabric/blob/HEAD/LICENSE"); + })); + pom.developers(developers -> developers.developer(developer -> { + developer.getName().set("FabricMC"); + developer.getUrl().set("https://fabricmc.net/"); + })); + pom.scm(scm -> { + scm.getConnection().set("scm:git:https://github.com/FabricMC/fabric.git"); + scm.getUrl().set("https://github.com/FabricMC/fabric"); + scm.getDeveloperConnection().set("scm:git:git@github.com:FabricMC/fabric.git"); + }); + pom.issueManagement(issueManagement -> { + issueManagement.getSystem().set("GitHub"); + issueManagement.getUrl().set("https://github.com/FabricMC/fabric/issues"); + }); + } + + public static void appendPomDependencies(Node pomNode, List> dependencies) { + Node depsNode = pomNode.appendNode("dependencies"); + + for (Map dependency : dependencies) { + Node depNode = depsNode.appendNode("dependency"); + + for (Entry entry : dependency.entrySet()) { + depNode.appendNode(entry.getKey(), entry.getValue()); + } + } + } + + private static String sha256Hex(String input) { + try { + byte[] digest = MessageDigest.getInstance("SHA-256").digest(input.getBytes(StandardCharsets.UTF_8)); + StringBuilder builder = new StringBuilder(digest.length * 2); + + for (byte value : digest) { + builder.append(String.format("%02x", value)); + } + + return builder.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Could not load SHA-256 digest", e); + } + } +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/FabricApiModuleExtension.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/FabricApiModuleExtension.java new file mode 100644 index 00000000000..b58bf61ca5b --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/FabricApiModuleExtension.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import groovy.util.Node; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.compile.JavaCompile; + +public class FabricApiModuleExtension { + private final Project project; + + public FabricApiModuleExtension(Project project) { + this.project = project; + } + + public void moduleDependencies(String... dependencyNames) { + project.getPluginManager().withPlugin("java", plugin -> configureModuleDependencies(List.of(dependencyNames))); + } + + public void testDependencies(String... dependencyNames) { + project.getPluginManager().withPlugin("java", plugin -> configureTestDependencies(List.of(dependencyNames))); + } + + private void configureModuleDependencies(List dependencyNames) { + List dependencyProjects = dependencyProjects(dependencyNames); + SourceSet clientSourceSet = sourceSets().getByName("client"); + + for (String dependencyName : dependencyNames) { + project.getDependencies().add("api", project.getDependencies().project(Map.of("path", FabricApiBuildUtils.projectPath(dependencyName)))); + } + + addDependencyClientOutputs("clientImplementation", clientSourceSet, dependencyProjects); + dependsOnDependencyClientClasses("compileClientJava", dependencyProjects); + addClientClassDirsToCompileTask("compileClientJava", dependencyProjects); + addClientClassDirsToCompileTask("compileTestmodClientJava", dependencyProjects); + + List> dependencyNodes = new ArrayList<>(); + + for (String dependencyName : dependencyNames) { + Project dependencyProject = project.getRootProject().findProject(FabricApiBuildUtils.projectPath(dependencyName)); + var dependencyNode = new LinkedHashMap(); + dependencyNode.put("groupId", dependencyProject.getGroup().toString()); + dependencyNode.put("artifactId", dependencyProject.getName()); + dependencyNode.put("version", FabricApiBuildUtils.moduleVersion(dependencyProject)); + dependencyNode.put("scope", "compile"); + dependencyNodes.add(dependencyNode); + } + + project.getPluginManager().withPlugin("maven-publish", plugin -> project.getExtensions().configure(PublishingExtension.class, publishing -> { + publishing.getPublications().named("mavenJava", MavenPublication.class).configure(publication -> { + publication.getPom().withXml(xml -> FabricApiBuildUtils.appendPomDependencies((Node) xml.asNode(), dependencyNodes)); + }); + })); + } + + private void configureTestDependencies(List dependencyNames) { + List dependencyProjects = dependencyProjects(dependencyNames); + SourceSet testmodClientSourceSet = sourceSets().getByName("testmodClient"); + + for (String dependencyName : dependencyNames) { + project.getDependencies().add("testmodImplementation", project.getDependencies().project(Map.of("path", FabricApiBuildUtils.projectPath(dependencyName)))); + } + + addDependencyClientOutputs("testmodClientImplementation", testmodClientSourceSet, dependencyProjects); + dependsOnDependencyClientClasses("compileTestmodClientJava", dependencyProjects); + addClientClassDirsToCompileTask("compileTestmodClientJava", dependencyProjects); + } + + private List dependencyProjects(List dependencyNames) { + return dependencyNames.stream() + .map(dependencyName -> project.getRootProject().findProject(FabricApiBuildUtils.projectPath(dependencyName))) + .toList(); + } + + private SourceSetContainer sourceSets() { + return project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); + } + + private void addDependencyClientOutputs(String configurationName, SourceSet sourceSet, List dependencyProjects) { + for (Project dependencyProject : dependencyProjects) { + FileCollection clientOutput = project.files( + dependencyProject.getLayout().getBuildDirectory().dir("classes/java/client"), + dependencyProject.getLayout().getBuildDirectory().dir("resources/client") + ); + project.getDependencies().add(configurationName, clientOutput); + sourceSet.setCompileClasspath(sourceSet.getCompileClasspath().plus(clientOutput)); + sourceSet.setRuntimeClasspath(sourceSet.getRuntimeClasspath().plus(clientOutput)); + } + } + + private void dependsOnDependencyClientClasses(String taskName, List dependencyProjects) { + project.getTasks().named(taskName, JavaCompile.class).configure(task -> { + for (Project dependencyProject : dependencyProjects) { + task.dependsOn(dependencyProject.getPath() + ":clientClasses"); + } + }); + } + + private void addClientClassDirsToCompileTask(String taskName, List dependencyProjects) { + List clientClassDirs = dependencyProjects.stream() + .map(dependencyProject -> dependencyProject.getLayout().getBuildDirectory().dir("classes/java/client")) + .toList(); + FileCollection clientClasses = project.files(clientClassDirs); + + project.getTasks().named(taskName, JavaCompile.class).configure(task -> task.setClasspath(task.getClasspath().plus(clientClasses))); + } +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/GeneratePackageInfosTask.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/GeneratePackageInfosTask.java new file mode 100644 index 00000000000..e1c3d37365e --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/GeneratePackageInfosTask.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; + +public abstract class GeneratePackageInfosTask extends DefaultTask { + @InputFile + public abstract RegularFileProperty getHeader(); + + @Input + public abstract Property getProjectName(); + + @SkipWhenEmpty + @InputDirectory + public abstract DirectoryProperty getSourceRoot(); + + @OutputDirectory + public abstract DirectoryProperty getOutputDir(); + + public GeneratePackageInfosTask() { + getProjectName().set(getProject().getName()); + } + + @TaskAction + public void run() throws IOException { + Path output = getOutputDir().get().getAsFile().toPath(); + deleteDirectory(output); + String headerText = Files.readString(getHeader().get().getAsFile().toPath(), StandardCharsets.UTF_8).trim(); + Path root = getSourceRoot().get().getAsFile().toPath(); + + Files.walkFileTree(root, new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException { + if (!containsJavaFile(directory)) { + return FileVisitResult.CONTINUE; + } + + Path existingPackageInfo = directory.resolve("package-info.java"); + + if (Files.exists(existingPackageInfo)) { + String text = Files.readString(existingPackageInfo, StandardCharsets.UTF_8); + + if (!text.contains("@NullMarked")) { + throw new RuntimeException("package-info.java " + existingPackageInfo + " is missing @NullMarked annotation."); + } + + return FileVisitResult.CONTINUE; + } + + Path relativePath = root.relativize(directory); + Path target = output.resolve(relativePath); + Files.createDirectories(target); + + String packageName = relativePath.toString().replace(java.io.File.separatorChar, '.'); + + if (packageName.equals("net.fabricmc.fabric.api.util") && getProjectName().get().equals("fabric-content-registries-v0")) { + return FileVisitResult.CONTINUE; + } + + boolean isImpl = relativePath.toString().matches("^(net[/\\\\]fabricmc[/\\\\]fabric[/\\\\](impl|mixin)).*"); + Files.writeString(target.resolve("package-info.java"), packageInfo(headerText, packageName, isImpl), StandardCharsets.UTF_8); + return FileVisitResult.CONTINUE; + } + }); + } + + private static boolean containsJavaFile(Path directory) throws IOException { + try (var stream = Files.list(directory)) { + return stream.anyMatch(path -> Files.isRegularFile(path) && path.getFileName().toString().endsWith(".java")); + } + } + + private String packageInfo(String headerText, String packageName, boolean isImpl) { + if (isImpl) { + return """ + %s + /** + * Implementation code for %s. + */ + @ApiStatus.Internal + @NullMarked + package %s; + + import org.jetbrains.annotations.ApiStatus; + import org.jspecify.annotations.NullMarked; + """.formatted(headerText, getProjectName().get(), packageName).stripIndent(); + } + + return """ + %s + /** + * API code for %s. + */ + @NullMarked + package %s; + + import org.jspecify.annotations.NullMarked; + """.formatted(headerText, getProjectName().get(), packageName).stripIndent(); + } + + private static void deleteDirectory(Path directory) throws IOException { + if (!Files.exists(directory)) { + return; + } + + Files.walkFileTree(directory, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/ValidateAnnotationsTask.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/ValidateAnnotationsTask.java new file mode 100644 index 00000000000..3d806d1efbb --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/ValidateAnnotationsTask.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.regex.Pattern; + +import org.gradle.api.tasks.SourceTask; +import org.gradle.api.tasks.TaskAction; + +public abstract class ValidateAnnotationsTask extends SourceTask { + private static final Pattern API_STATUS_INTERNAL = Pattern.compile("@ApiStatus\\.Internal"); + private static final Pattern ENVIRONMENT = Pattern.compile("@Environment"); + + @TaskAction + public void run() { + for (String directory : List.of("api", "impl", "mixin", "test")) { + getSource().matching(pattern -> pattern.include("net/fabricmc/fabric/" + directory + "/**/*.java")).forEach(file -> { + if (file.isDirectory()) { + return; + } + + String contents; + + try { + contents = java.nio.file.Files.readString(file.toPath(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Could not read file: " + file, e); + } + + if (ENVIRONMENT.matcher(contents).find()) { + throw new RuntimeException("Found @Environment annotation in file: " + file); + } + + if (!directory.equals("api") && API_STATUS_INTERNAL.matcher(contents).find()) { + throw new RuntimeException("Found @ApiStatus.Internal in implementation file: " + file); + } + }); + } + } +} diff --git a/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/ValidateModuleTask.java b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/ValidateModuleTask.java new file mode 100644 index 00000000000..27f6de4071d --- /dev/null +++ b/buildSrc/src/main/java/net/fabricmc/fabric/impl/build/ValidateModuleTask.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.build; + +import java.io.File; +import java.util.Map; + +import groovy.json.JsonSlurper; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.TaskAction; + +public abstract class ValidateModuleTask extends DefaultTask { + @InputFile + public abstract RegularFileProperty getFmj(); + + @Input + public abstract Property getProjectName(); + + @Input + public abstract Property getProjectPath(); + + @Input + public abstract Property getLoaderVersion(); + + public ValidateModuleTask() { + setGroup("verification"); + getOutputs().upToDateWhen(task -> true); + + File file = getProject().file("src/main/resources/fabric.mod.json"); + + if (!file.exists()) { + file = getProject().file("src/client/resources/fabric.mod.json"); + } + + getFmj().set(file); + getProjectName().set(getProject().getName()); + getProjectPath().set(getProject().getPath()); + getLoaderVersion().set(FabricApiBuildUtils.version(getProject(), "fabric-loader")); + } + + @TaskAction + public void validate() { + @SuppressWarnings("unchecked") + Map json = (Map) new JsonSlurper().parse(getFmj().get().getAsFile()); + Object customObject = json.get("custom"); + + if (customObject == null) { + throw new GradleException("Module " + getProjectName().get() + " does not have a custom value containing module lifecycle!"); + } + + @SuppressWarnings("unchecked") + Map custom = (Map) customObject; + Object moduleLifecycle = custom.get("fabric-api:module-lifecycle"); + + if (moduleLifecycle == null) { + throw new GradleException("Module " + getProjectName().get() + " does not have module lifecycle in custom values!"); + } + + if (!(moduleLifecycle instanceof String)) { + throw new GradleException("Module " + getProjectName().get() + " has an invalid module lifecycle value. The value must be a string but read a " + moduleLifecycle.getClass()); + } + + switch ((String) moduleLifecycle) { + case "stable", "experimental" -> { } + case "deprecated" -> { + if (!getProjectPath().get().startsWith(":deprecated")) { + throw new GradleException("Deprecated module " + getProjectName().get() + " must be in the deprecated sub directory."); + } + } + default -> throw new GradleException("Module " + getProjectName().get() + " has an invalid module lifecycle " + moduleLifecycle); + } + + Object dependsObject = json.get("depends"); + + if (dependsObject == null) { + throw new GradleException("Module " + getProjectName().get() + " does not have a depends value!"); + } + + @SuppressWarnings("unchecked") + Map depends = (Map) dependsObject; + String expectedLoaderVersion = ">=" + getLoaderVersion().get(); + + if (!expectedLoaderVersion.equals(depends.get("fabricloader"))) { + throw new GradleException("Module " + getProjectName().get() + " does not have a valid fabricloader value! Got \"" + depends.get("fabricloader") + "\" but expected \"" + expectedLoaderVersion + "\""); + } + } +} diff --git a/deprecated/build.gradle b/deprecated/build.gradle index 5dbef4654d7..e53f3e6e28a 100644 --- a/deprecated/build.gradle +++ b/deprecated/build.gradle @@ -1,46 +1,3 @@ -/** - * This project generates a maven bill of materials (BOM) that includes the deprecated modules, alongside the main project. - */ -version = rootProject.version - -publishing { - publications { - mavenJava(MavenPublication) { - artifactId = 'fabric-api-deprecated' - - List> dependencies = [] - - allprojects.each { - if (it.name == "deprecated") { - return // Dont depend on yourself :) - } - - // Depend on all of the deprecated projects - if (!it.path.startsWith(":deprecated")) { - 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']) - } - } - } - } +plugins { + id 'fabric-api.deprecated-bom' } diff --git a/deprecated/fabric-resource-loader-v0/build.gradle b/deprecated/fabric-resource-loader-v0/build.gradle index 762ea15d120..e4ea818fb85 100644 --- a/deprecated/fabric-resource-loader-v0/build.gradle +++ b/deprecated/fabric-resource-loader-v0/build.gradle @@ -1,10 +1,16 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, ['fabric-api-base', 'fabric-resource-loader-v1']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', 'fabric-resource-loader-v1' + ) -testDependencies(project, [ - ':fabric-lifecycle-events-v1', - ':fabric-api-base', - ':fabric-gametest-api-v1', - ':deprecated:fabric-resource-loader-v0' -]) + testDependencies( + ':fabric-lifecycle-events-v1', + ':fabric-api-base', + ':fabric-gametest-api-v1', + ':deprecated:fabric-resource-loader-v0' + ) +} diff --git a/fabric-api-base/build.gradle b/fabric-api-base/build.gradle index fc1eb105c53..bacb253c889 100644 --- a/fabric-api-base/build.gradle +++ b/fabric-api-base/build.gradle @@ -1,7 +1,11 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -testDependencies(project, [ - ':fabric-command-api-v2', - ':fabric-lifecycle-events-v1', - ':fabric-screen-api-v1' -]) +fabricApiModule { + testDependencies( + ':fabric-command-api-v2', + ':fabric-lifecycle-events-v1', + ':fabric-screen-api-v1' + ) +} diff --git a/fabric-api-bom/build.gradle b/fabric-api-bom/build.gradle index 2a48fd7f8d4..97cf0776011 100644 --- a/fabric-api-bom/build.gradle +++ b/fabric-api-bom/build.gradle @@ -1,31 +1,3 @@ plugins { - id 'java-platform' -} - -version = rootProject.version - -publishing.publications { - register('mavenJava', MavenPublication) { - from(components['javaPlatform']) - } -} - -tasks.withType(GenerateModuleMetadata) { - // todo: RemoteSignJar fails when this is false (as set by parent build script) - enabled = true -} - -dependencies { - constraints { - for (proj in rootProject.allprojects) { - if (proj == project) { // the bom itself - continue - } - if (proj.name == 'fabric-api-catalog') { - continue - } - - api(project(proj.path)) - } - } + id 'fabric-api.maven-bom' } diff --git a/fabric-api-catalog/build.gradle b/fabric-api-catalog/build.gradle index 18f4ce38882..bc77070d771 100644 --- a/fabric-api-catalog/build.gradle +++ b/fabric-api-catalog/build.gradle @@ -1,5 +1,8 @@ +import org.gradle.api.publish.maven.MavenPublication + plugins { - id 'version-catalog' + id "version-catalog" + id "fabric-api.base-publishing" } version = rootProject.version @@ -10,12 +13,10 @@ publishing.publications { } } -tasks.withType(GenerateModuleMetadata) { - // todo: RemoteSignJar fails when this is false (as set by parent build script) +tasks.withType(GenerateModuleMetadata).configureEach { enabled = true } -// Avoid configuration ordering issues by creating the catalog entries during task execution time tasks.register('configureCatalog') { doConfigureCatalog() } @@ -26,7 +27,7 @@ tasks.named('generateCatalogAsToml') { def doConfigureCatalog() { for (proj in rootProject.allprojects) { - if (proj == project) { // the catalog itself + if (proj == project) { continue } diff --git a/fabric-api-lookup-api-v1/build.gradle b/fabric-api-lookup-api-v1/build.gradle index 860d2b0801a..8a6a136a089 100644 --- a/fabric-api-lookup-api-v1/build.gradle +++ b/fabric-api-lookup-api-v1/build.gradle @@ -1,12 +1,16 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-lifecycle-events-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-lifecycle-events-v1' + ) -testDependencies(project, [ - ':fabric-rendering-v1', - ':fabric-object-builder-api-v1', - ':fabric-transitive-access-wideners-v1' -]) + testDependencies( + ':fabric-rendering-v1', + ':fabric-object-builder-api-v1', + ':fabric-transitive-access-wideners-v1' + ) +} diff --git a/fabric-biome-api-v1/build.gradle b/fabric-biome-api-v1/build.gradle index a5903807f4f..7f8dcaf6630 100644 --- a/fabric-biome-api-v1/build.gradle +++ b/fabric-biome-api-v1/build.gradle @@ -1,15 +1,19 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-biome-api-v1.classtweaker") } -testDependencies(project, [ - ':fabric-api-base', - ':fabric-resource-loader-v1', - ':fabric-registry-sync-v0', - ':fabric-data-generation-api-v1' -]) +fabricApiModule { + testDependencies( + ':fabric-api-base', + ':fabric-resource-loader-v1', + ':fabric-registry-sync-v0', + ':fabric-data-generation-api-v1' + ) +} fabricApi { configureDataGeneration { diff --git a/fabric-block-api-v1/build.gradle b/fabric-block-api-v1/build.gradle index 1912cf8d419..c3b1b47059b 100644 --- a/fabric-block-api-v1/build.gradle +++ b/fabric-block-api-v1/build.gradle @@ -1,9 +1,13 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-block-api-v1.classtweaker') } -testDependencies(project, [ - ':fabric-rendering-v1', -]) +fabricApiModule { + testDependencies( + ':fabric-rendering-v1', + ) +} diff --git a/fabric-block-getter-api-v2/build.gradle b/fabric-block-getter-api-v2/build.gradle index 79e8927a39a..491895f0e10 100644 --- a/fabric-block-getter-api-v2/build.gradle +++ b/fabric-block-getter-api-v2/build.gradle @@ -1,4 +1,6 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-block-getter-api-v2.classtweaker') diff --git a/fabric-client-gametest-api-v1/build.gradle b/fabric-client-gametest-api-v1/build.gradle index da0c75e18ab..9cab8e1393b 100644 --- a/fabric-client-gametest-api-v1/build.gradle +++ b/fabric-client-gametest-api-v1/build.gradle @@ -1,28 +1,8 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' + id 'fabric-api.mixin-config' +} loom { accessWidenerPath = file('src/client/resources/fabric-client-gametest-api-v1.classtweaker') } - -sourceSets { - mixinConfig { - compileClasspath += configurations.loaderLibraries - } - client { - compileClasspath += mixinConfig.output - runtimeClasspath += mixinConfig.output - } -} - -configurations { - clientImplementation.extendsFrom mixinConfigImplementation - clientRuntimeOnly.extendsFrom mixinConfigRuntimeOnly -} - -dependencies { - mixinConfigImplementation "net.fabricmc:fabric-loader:${project.loader_version}" -} - -jar { - from sourceSets.mixinConfig.output -} diff --git a/fabric-command-api-v2/build.gradle b/fabric-command-api-v2/build.gradle index 2c057b35e78..3f23f875b9e 100644 --- a/fabric-command-api-v2/build.gradle +++ b/fabric-command-api-v2/build.gradle @@ -1,10 +1,16 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) -testDependencies(project, [ - ':fabric-lifecycle-events-v1', -]) + testDependencies( + ':fabric-lifecycle-events-v1', + ) +} loom { accessWidenerPath = file('src/main/resources/fabric-command-api-v2.classtweaker') diff --git a/fabric-content-registries-v0/build.gradle b/fabric-content-registries-v0/build.gradle index 29381d306e9..e54b688847d 100644 --- a/fabric-content-registries-v0/build.gradle +++ b/fabric-content-registries-v0/build.gradle @@ -1,11 +1,15 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-content-registries-v0.classtweaker") } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-lifecycle-events-v1', - 'fabric-resource-loader-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-lifecycle-events-v1', + 'fabric-resource-loader-v1' + ) +} diff --git a/fabric-convention-tags-v2/build.gradle b/fabric-convention-tags-v2/build.gradle index 131ee71c836..587af7fbb9e 100644 --- a/fabric-convention-tags-v2/build.gradle +++ b/fabric-convention-tags-v2/build.gradle @@ -1,17 +1,21 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-convention-tags-v2.classtweaker') } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-lifecycle-events-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-lifecycle-events-v1' + ) -testDependencies(project, [ - ':fabric-lifecycle-events-v1', -]) + testDependencies( + ':fabric-lifecycle-events-v1', + ) +} fabricApi { configureDataGeneration { diff --git a/fabric-crash-report-info-v1/build.gradle b/fabric-crash-report-info-v1/build.gradle index 64f55fd09ee..ecf1ef16bbe 100644 --- a/fabric-crash-report-info-v1/build.gradle +++ b/fabric-crash-report-info-v1/build.gradle @@ -1,5 +1,9 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -testDependencies(project, [ - ':fabric-command-api-v2' -]) +fabricApiModule { + testDependencies( + ':fabric-command-api-v2' + ) +} diff --git a/fabric-creative-tab-api-v1/build.gradle b/fabric-creative-tab-api-v1/build.gradle index b414c7cc588..326b92d1f88 100644 --- a/fabric-creative-tab-api-v1/build.gradle +++ b/fabric-creative-tab-api-v1/build.gradle @@ -1,9 +1,13 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-resource-loader-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-resource-loader-v1' + ) +} loom { accessWidenerPath = file('src/main/resources/fabric-creative-tab-api-v1.classtweaker') diff --git a/fabric-data-attachment-api-v1/build.gradle b/fabric-data-attachment-api-v1/build.gradle index 1cf1afa68e4..b53949d440a 100644 --- a/fabric-data-attachment-api-v1/build.gradle +++ b/fabric-data-attachment-api-v1/build.gradle @@ -1,21 +1,25 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-data-attachment-api-v1.classtweaker') } -moduleDependencies(project, [ - 'fabric-api-base', - ':fabric-entity-events-v1', - ':fabric-object-builder-api-v1', - ':fabric-networking-api-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + ':fabric-entity-events-v1', + ':fabric-object-builder-api-v1', + ':fabric-networking-api-v1' + ) -testDependencies(project, [ - ':fabric-lifecycle-events-v1', - ':fabric-biome-api-v1', - ':fabric-command-api-v2', - ':fabric-rendering-v1', - ':fabric-client-gametest-api-v1', - ':fabric-events-interaction-v0', -]) + testDependencies( + ':fabric-lifecycle-events-v1', + ':fabric-biome-api-v1', + ':fabric-command-api-v2', + ':fabric-rendering-v1', + ':fabric-client-gametest-api-v1', + ':fabric-events-interaction-v0', + ) +} diff --git a/fabric-data-generation-api-v1/build.gradle b/fabric-data-generation-api-v1/build.gradle index 8e37efb8f17..371f883c403 100644 --- a/fabric-data-generation-api-v1/build.gradle +++ b/fabric-data-generation-api-v1/build.gradle @@ -1,17 +1,29 @@ -version = getSubprojectVersion(project) +import net.fabricmc.fabric.impl.build.AbstractGenerateClassTweakerTask -moduleDependencies(project, [ - 'fabric-convention-tags-v2', - 'fabric-recipe-api-v1', - 'fabric-registry-sync-v0', - 'fabric-resource-conditions-api-v1', - 'fabric-tag-api-v1', -]) +import org.gradle.api.tasks.TaskAction +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode + +import java.lang.reflect.Modifier + +plugins { + id 'fabric-api.module' +} -testDependencies(project, [ - ':fabric-creative-tab-api-v1', - ':fabric-object-builder-api-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-convention-tags-v2', + 'fabric-recipe-api-v1', + 'fabric-registry-sync-v0', + 'fabric-resource-conditions-api-v1', + 'fabric-tag-api-v1', + ) + + testDependencies( + ':fabric-creative-tab-api-v1', + ':fabric-object-builder-api-v1' + ) +} dependencies { } @@ -72,145 +84,119 @@ tasks.register('datapackZip', Zip) { build.dependsOn datapackZip -import org.objectweb.asm.ClassReader -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.ClassNode +abstract class GenerateDataClassTweakerTask extends AbstractGenerateClassTweakerTask { + @TaskAction + void run() { + def classes = readClasses() + def task = this + List lines = new ArrayList<>() + addHeader(lines, "classTweaker\tv1\tofficial", true) -import java.lang.reflect.Modifier -import java.util.zip.ZipEntry -import java.util.zip.ZipFile - -tasks.register('generateClassTweaker') { - inputs.files(loom.getNamedMinecraftJars()) - - doLast { - // Use parent provider to get the jar before the AWs are applied - def minecraftProvider = loom.namedMinecraftProvider.parentMinecraftProvider - def commonJar = minecraftProvider.commonJar.path.toFile() - def clientJar = minecraftProvider.clientOnlyJar.path.toFile() - def classes = getClasses([commonJar, clientJar]) - - String out = "classTweaker\tv1\tofficial\n" - out += "\n" - out += "# DO NOT EDIT BY HAND! This file is generated automatically.\n" - out += "# Edit \"template.classtweaker\" instead then run \"gradlew generateClassTweaker\".\n" - out += "\n" - out += file("template.classtweaker").text + "\n" - - visitMethods(classes["net/minecraft/data/recipes/RecipeProvider"]) { name, desc, owner -> - if (it.name == "generate") + visitMethods(classes.get("net/minecraft/data/recipes/RecipeProvider")) { method, owner -> + if (method.name == "generate") { return + } - out += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" + task.addMethod(lines, "transitive-accessible", owner, method) } - visitMethods(classes["net/minecraft/client/data/models/BlockModelGenerators"]) { name, desc, owner -> - if (desc == "()V") - // Skip over methods that dont take any arguments, as they are specific to minecraft. + visitMethods(classes.get("net/minecraft/client/data/models/BlockModelGenerators")) { method, owner -> + if (method.desc == "()V") { return + } - out += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" + task.addMethod(lines, "transitive-accessible", owner, method) } - visitMethods(classes["net/minecraft/data/loot/BlockLootSubProvider"]) { name, desc, owner -> - out += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" + visitMethods(classes.get("net/minecraft/data/loot/BlockLootSubProvider")) { method, owner -> + task.addMethod(lines, "transitive-accessible", owner, method) } - visitMethods(classes["net/minecraft/client/data/models/ItemModelGenerators"]) { name, desc, owner -> - out += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" + visitMethods(classes.get("net/minecraft/client/data/models/ItemModelGenerators")) { method, owner -> + task.addMethod(lines, "transitive-accessible", owner, method) } classes.values().forEach { classNode -> - visitFinalMethods(classNode) { name, desc, owner -> - if (name != "getName" || desc != "()Ljava/lang/String;") { - // Not the method we are after + visitFinalMethods(classNode) { method, owner -> + if (method.name != "getName" || method.desc != "()Ljava/lang/String;") { return } - if (!hasAncestor(classNode, classes, "net/minecraft/data/DataProvider")) { - // Not a descendant of DataProvider + if (!task.hasAncestor(classNode, classes, "net/minecraft/data/DataProvider")) { return } - out += "transitive-extendable\tmethod\t${owner}\t${name}\t${desc}\n" + task.addMethod(lines, "transitive-extendable", owner, method) } } - file("src/main/resources/fabric-data-generation-api-v1.classtweaker").text = out + writeOutput(lines) } -} - -static def visitMethods(ClassNode classNode, closure) { - classNode.methods.forEach { - if ((it.access & Opcodes.ACC_SYNTHETIC) != 0 || (it.access & Opcodes.ACC_PUBLIC) != 0) - return - - if (it.name.startsWith("<")) - return - closure(it.name, it.desc, classNode.name) + void addMethod(List lines, String access, String owner, method) { + lines.add("${access}\tmethod\t${owner}\t${method.name}\t${method.desc}") } -} -static def visitFinalMethods(ClassNode classNode, closure) { - classNode.methods.forEach { - if (!Modifier.isFinal(it.access)) - return + void visitMethods(ClassNode classNode, Closure visitor) { + classNode.methods.forEach { method -> + if ((method.access & Opcodes.ACC_SYNTHETIC) != 0 || (method.access & Opcodes.ACC_PUBLIC) != 0) { + return + } - if (it.name.startsWith("<")) - return + if (method.name.startsWith("<")) { + return + } - closure(it.name, it.desc, classNode.name) + visitor(method, classNode.name) + } } -} - -// Return a map of all class names to classNodes -static def getClasses(List inputs) { - Map classes = new TreeMap<>() - for (File input : inputs) { - new ZipFile(input).withCloseable { ZipFile zip -> - zip.entries().toList().forEach { ZipEntry entry -> - if (!entry.name.endsWith(".class")) { - return - } - - zip.getInputStream(entry).withCloseable { is -> - ClassReader reader = new ClassReader(is) - ClassNode classNode = new ClassNode() - reader.accept(classNode, ClassReader.SKIP_CODE) + void visitFinalMethods(ClassNode classNode, Closure visitor) { + classNode.methods.forEach { method -> + if (!Modifier.isFinal(method.access)) { + return + } - classes.put(classNode.name, classNode) - } + if (method.name.startsWith("<")) { + return } + + visitor(method, classNode.name) } } - return classes -} - -def hasAncestor(ClassNode classNode, Map classes, String ancestorName) { - if (classNode.superName == ancestorName) { - return true - } + boolean hasAncestor(ClassNode classNode, Map classes, String ancestorName) { + if (classNode.superName == ancestorName) { + return true + } - // Recuse through the super classes - def superClass = classes.get(classNode.superName) - if (superClass != null && hasAncestor(superClass, classes, ancestorName)) { - return true - } + def superClass = classes.get(classNode.superName) - for (def interfaceName : classNode.interfaces) { - if (interfaceName == ancestorName) { + if (superClass != null && hasAncestor(superClass, classes, ancestorName)) { return true } - def ifaceClass = classes.get(interfaceName) - if (ifaceClass != null && hasAncestor(ifaceClass, classes, ancestorName)) { - return true + for (String interfaceName : classNode.interfaces) { + if (interfaceName == ancestorName) { + return true + } + + def ifaceClass = classes.get(interfaceName) + + if (ifaceClass != null && hasAncestor(ifaceClass, classes, ancestorName)) { + return true + } } + + return false } } +tasks.register('generateClassTweaker', GenerateDataClassTweakerTask) { + template = file("template.classtweaker") + outputFile = file("src/main/resources/fabric-data-generation-api-v1.classtweaker") +} + generateResources.dependsOn generateClassTweaker generateResources.dependsOn runDatagenClient +processResources.mustRunAfter generateClassTweaker diff --git a/fabric-debug-api-v1/build.gradle b/fabric-debug-api-v1/build.gradle index 91ac94ae90f..2b682681b0a 100644 --- a/fabric-debug-api-v1/build.gradle +++ b/fabric-debug-api-v1/build.gradle @@ -1,11 +1,14 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) -testDependencies(project, [ -]) + testDependencies() +} loom { accessWidenerPath = file("src/main/resources/fabric-debug-api-v1.accesswidener") diff --git a/fabric-dimensions-v1/build.gradle b/fabric-dimensions-v1/build.gradle index 8bfe314df96..44abeeac62b 100644 --- a/fabric-dimensions-v1/build.gradle +++ b/fabric-dimensions-v1/build.gradle @@ -1,11 +1,15 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-lifecycle-events-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-lifecycle-events-v1' + ) -testDependencies(project, [ - ':fabric-resource-loader-v1', - ':fabric-client-gametest-api-v1' -]) + testDependencies( + ':fabric-resource-loader-v1', + ':fabric-client-gametest-api-v1' + ) +} diff --git a/fabric-entity-events-v1/build.gradle b/fabric-entity-events-v1/build.gradle index 54d84046d6c..2f81a2d7758 100644 --- a/fabric-entity-events-v1/build.gradle +++ b/fabric-entity-events-v1/build.gradle @@ -1,14 +1,20 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-entity-events-v1.classtweaker') } -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) -testDependencies(project, [ - ':fabric-command-api-v2', - ':fabric-networking-api-v1', - ':fabric-registry-sync-v0', - ':fabric-rendering-v1' -]) + testDependencies( + ':fabric-command-api-v2', + ':fabric-networking-api-v1', + ':fabric-registry-sync-v0', + ':fabric-rendering-v1' + ) +} diff --git a/fabric-events-interaction-v0/build.gradle b/fabric-events-interaction-v0/build.gradle index 5ea804ac6f6..bdf9d896202 100644 --- a/fabric-events-interaction-v0/build.gradle +++ b/fabric-events-interaction-v0/build.gradle @@ -1,7 +1,13 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, ['fabric-api-base', 'fabric-networking-api-v1']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', 'fabric-networking-api-v1' + ) -testDependencies(project, [ - 'fabric-client-gametest-api-v1' -]) + testDependencies( + 'fabric-client-gametest-api-v1' + ) +} diff --git a/fabric-game-rule-api-v1/build.gradle b/fabric-game-rule-api-v1/build.gradle index a6858660bd9..d831fdfcdc9 100644 --- a/fabric-game-rule-api-v1/build.gradle +++ b/fabric-game-rule-api-v1/build.gradle @@ -1,13 +1,19 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-game-rule-api-v1.classtweaker") } -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) -testDependencies(project, [ - ':fabric-api-base', - ':fabric-lifecycle-events-v1', - ':fabric-resource-loader-v1' -]) + testDependencies( + ':fabric-api-base', + ':fabric-lifecycle-events-v1', + ':fabric-resource-loader-v1' + ) +} diff --git a/fabric-gametest-api-v1/build.gradle b/fabric-gametest-api-v1/build.gradle index 08e8c3d9fa5..8e499012b17 100644 --- a/fabric-gametest-api-v1/build.gradle +++ b/fabric-gametest-api-v1/build.gradle @@ -1,4 +1,6 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-gametest-api-v1.classtweaker") @@ -15,8 +17,10 @@ loom { } } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-registry-sync-v0', - 'fabric-resource-loader-v1', -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-registry-sync-v0', + 'fabric-resource-loader-v1', + ) +} diff --git a/fabric-item-api-v1/build.gradle b/fabric-item-api-v1/build.gradle index 6ab22b6e207..6dd9613f946 100644 --- a/fabric-item-api-v1/build.gradle +++ b/fabric-item-api-v1/build.gradle @@ -1,11 +1,17 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-item-api-v1.classtweaker") } -moduleDependencies(project, ['fabric-api-base', 'fabric-resource-loader-v1']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', 'fabric-resource-loader-v1' + ) -testDependencies(project, [ - ':fabric-content-registries-v0', -]) + testDependencies( + ':fabric-content-registries-v0', + ) +} diff --git a/fabric-key-mapping-api-v1/build.gradle b/fabric-key-mapping-api-v1/build.gradle index 68b99e5db0f..732c893ca7a 100644 --- a/fabric-key-mapping-api-v1/build.gradle +++ b/fabric-key-mapping-api-v1/build.gradle @@ -1,7 +1,11 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -testDependencies(project, [ - ':fabric-api-base', - ':fabric-lifecycle-events-v1', - ':fabric-resource-loader-v1' -]) +fabricApiModule { + testDependencies( + ':fabric-api-base', + ':fabric-lifecycle-events-v1', + ':fabric-resource-loader-v1' + ) +} diff --git a/fabric-lifecycle-events-v1/build.gradle b/fabric-lifecycle-events-v1/build.gradle index 7178d1ef763..205f91188ba 100644 --- a/fabric-lifecycle-events-v1/build.gradle +++ b/fabric-lifecycle-events-v1/build.gradle @@ -1,7 +1,13 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-lifecycle-events-v1.classtweaker") } -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) +} diff --git a/fabric-loot-api-v3/build.gradle b/fabric-loot-api-v3/build.gradle index ccb92c75706..8864f5039d2 100644 --- a/fabric-loot-api-v3/build.gradle +++ b/fabric-loot-api-v3/build.gradle @@ -1,10 +1,14 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-loot-api-v3.classtweaker') } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-resource-loader-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-resource-loader-v1' + ) +} diff --git a/fabric-menu-api-v1/build.gradle b/fabric-menu-api-v1/build.gradle index 7f20fc87538..94f1375197a 100644 --- a/fabric-menu-api-v1/build.gradle +++ b/fabric-menu-api-v1/build.gradle @@ -1,17 +1,21 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-menu-api-v1.classtweaker') } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-networking-api-v1', - 'fabric-registry-sync-v0' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-networking-api-v1', + 'fabric-registry-sync-v0' + ) -testDependencies(project, [ - ':fabric-object-builder-api-v1', - ':fabric-resource-loader-v1', - ':fabric-transitive-access-wideners-v1' -]) + testDependencies( + ':fabric-object-builder-api-v1', + ':fabric-resource-loader-v1', + ':fabric-transitive-access-wideners-v1' + ) +} diff --git a/fabric-message-api-v1/build.gradle b/fabric-message-api-v1/build.gradle index a733beb92bb..365271edb25 100644 --- a/fabric-message-api-v1/build.gradle +++ b/fabric-message-api-v1/build.gradle @@ -1,5 +1,13 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) -testDependencies(project, ['fabric-command-api-v2']) + testDependencies( + 'fabric-command-api-v2' + ) +} diff --git a/fabric-model-loading-api-v1/build.gradle b/fabric-model-loading-api-v1/build.gradle index 84af2960727..853f589aa64 100644 --- a/fabric-model-loading-api-v1/build.gradle +++ b/fabric-model-loading-api-v1/build.gradle @@ -1,16 +1,20 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-renderer-api-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-renderer-api-v1' + ) -testDependencies(project, [ - ':fabric-renderer-api-v1', - ':fabric-renderer-indigo', - ':fabric-rendering-v1', - ':fabric-resource-loader-v1' -]) + testDependencies( + ':fabric-renderer-api-v1', + ':fabric-renderer-indigo', + ':fabric-rendering-v1', + ':fabric-resource-loader-v1' + ) +} loom { accessWidenerPath = file('src/client/resources/fabric-model-loading-api-v1.classtweaker') diff --git a/fabric-networking-api-v1/build.gradle b/fabric-networking-api-v1/build.gradle index c9dbcbccf00..94eb43953f3 100644 --- a/fabric-networking-api-v1/build.gradle +++ b/fabric-networking-api-v1/build.gradle @@ -1,12 +1,18 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) -testDependencies(project, [ - ':fabric-command-api-v2', - ':fabric-lifecycle-events-v1', - ':fabric-key-mapping-api-v1' -]) + testDependencies( + ':fabric-command-api-v2', + ':fabric-lifecycle-events-v1', + ':fabric-key-mapping-api-v1' + ) +} loom { accessWidenerPath = file('src/main/resources/fabric-networking-api-v1.classtweaker') diff --git a/fabric-object-builder-api-v1/build.gradle b/fabric-object-builder-api-v1/build.gradle index 8c4fde6e552..1c9660e3f2c 100644 --- a/fabric-object-builder-api-v1/build.gradle +++ b/fabric-object-builder-api-v1/build.gradle @@ -1,16 +1,20 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-registry-sync-v0', - 'fabric-resource-loader-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-registry-sync-v0', + 'fabric-resource-loader-v1' + ) -testDependencies(project, [ - ':fabric-command-api-v2', - ':fabric-lifecycle-events-v1', - ':fabric-rendering-v1' -]) + testDependencies( + ':fabric-command-api-v2', + ':fabric-lifecycle-events-v1', + ':fabric-rendering-v1' + ) +} loom { accessWidenerPath = file("src/main/resources/fabric-object-builder-api-v1.classtweaker") diff --git a/fabric-particles-v1/build.gradle b/fabric-particles-v1/build.gradle index 70fafde28bc..31fbf309a23 100644 --- a/fabric-particles-v1/build.gradle +++ b/fabric-particles-v1/build.gradle @@ -1,19 +1,23 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-particles-v1.classtweaker") } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-networking-api-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-networking-api-v1' + ) -testDependencies(project, [ + testDependencies( ':fabric-command-api-v2', ':fabric-rendering-v1', ':fabric-resource-loader-v1' -]) + ) +} validateMixinNames { // Loom needs to handle inner mixins better diff --git a/fabric-permission-api-v1/build.gradle b/fabric-permission-api-v1/build.gradle index b86e587dda3..f3946ad727f 100644 --- a/fabric-permission-api-v1/build.gradle +++ b/fabric-permission-api-v1/build.gradle @@ -1,15 +1,19 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-permission-api-v1.classtweaker') } -moduleDependencies(project, [ - ":fabric-api-base" -]) +fabricApiModule { + moduleDependencies( + ":fabric-api-base" + ) -testDependencies(project, [ - ":fabric-command-api-v2", - ":fabric-lifecycle-events-v1", - ":fabric-networking-api-v1" -]) + testDependencies( + ":fabric-command-api-v2", + ":fabric-lifecycle-events-v1", + ":fabric-networking-api-v1" + ) +} diff --git a/fabric-recipe-api-v1/build.gradle b/fabric-recipe-api-v1/build.gradle index c00c3fd0a61..7be25c7fa9e 100644 --- a/fabric-recipe-api-v1/build.gradle +++ b/fabric-recipe-api-v1/build.gradle @@ -1,15 +1,19 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-recipe-api-v1.classtweaker') } -moduleDependencies(project, [ - ':fabric-lifecycle-events-v1', - 'fabric-networking-api-v1', -]) +fabricApiModule { + moduleDependencies( + ':fabric-lifecycle-events-v1', + 'fabric-networking-api-v1', + ) -testDependencies(project, [ - ':fabric-lifecycle-events-v1', - ':fabric-registry-sync-v0', -]) + testDependencies( + ':fabric-lifecycle-events-v1', + ':fabric-registry-sync-v0', + ) +} diff --git a/fabric-registry-sync-v0/build.gradle b/fabric-registry-sync-v0/build.gradle index a1ee9ba16aa..7a7eb3294c9 100644 --- a/fabric-registry-sync-v0/build.gradle +++ b/fabric-registry-sync-v0/build.gradle @@ -1,15 +1,19 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-registry-sync-v0.classtweaker") } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-networking-api-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-networking-api-v1' + ) -testDependencies(project, [ - ':fabric-lifecycle-events-v1', - ':fabric-command-api-v2', -]) + testDependencies( + ':fabric-lifecycle-events-v1', + ':fabric-command-api-v2', + ) +} diff --git a/fabric-renderer-api-v1/build.gradle b/fabric-renderer-api-v1/build.gradle index d18e011e2b6..99acf39880d 100644 --- a/fabric-renderer-api-v1/build.gradle +++ b/fabric-renderer-api-v1/build.gradle @@ -1,21 +1,25 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - ':fabric-api-base', - ':fabric-rendering-v1', - ':fabric-transitive-access-wideners-v1' -]) +fabricApiModule { + moduleDependencies( + ':fabric-api-base', + ':fabric-rendering-v1', + ':fabric-transitive-access-wideners-v1' + ) -testDependencies(project, [ - ':fabric-block-api-v1', - ':fabric-block-getter-api-v2', - ':fabric-model-loading-api-v1', - ':fabric-object-builder-api-v1', - ':fabric-particles-v1', - ':fabric-renderer-indigo', - ':fabric-rendering-v1', - ':fabric-resource-loader-v1' -]) + testDependencies( + ':fabric-block-api-v1', + ':fabric-block-getter-api-v2', + ':fabric-model-loading-api-v1', + ':fabric-object-builder-api-v1', + ':fabric-particles-v1', + ':fabric-renderer-indigo', + ':fabric-rendering-v1', + ':fabric-resource-loader-v1' + ) +} loom { accessWidenerPath = file('src/client/resources/fabric-renderer-api-v1.classtweaker') diff --git a/fabric-renderer-indigo/build.gradle b/fabric-renderer-indigo/build.gradle index 12885b51b09..db8bad2fb2c 100644 --- a/fabric-renderer-indigo/build.gradle +++ b/fabric-renderer-indigo/build.gradle @@ -1,34 +1,16 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' + id 'fabric-api.mixin-config' +} loom { accessWidenerPath = file("src/client/resources/fabric-renderer-indigo.classtweaker") } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-renderer-api-v1', - 'fabric-rendering-v1' -]) - -sourceSets { - mixinConfig { - compileClasspath += configurations.loaderLibraries - } - client { - compileClasspath += mixinConfig.output - runtimeClasspath += mixinConfig.output - } -} - -configurations { - clientImplementation.extendsFrom mixinConfigImplementation - clientRuntimeOnly.extendsFrom mixinConfigRuntimeOnly -} - -dependencies { - mixinConfigImplementation "net.fabricmc:fabric-loader:${project.loader_version}" -} - -jar { - from sourceSets.mixinConfig.output +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-renderer-api-v1', + 'fabric-rendering-v1' + ) } diff --git a/fabric-rendering-fluids-v1/build.gradle b/fabric-rendering-fluids-v1/build.gradle index 409b49f2d14..cd865de95e0 100644 --- a/fabric-rendering-fluids-v1/build.gradle +++ b/fabric-rendering-fluids-v1/build.gradle @@ -1,7 +1,13 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-rendering-fluids-v1.classtweaker') } -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) +} diff --git a/fabric-rendering-v1/build.gradle b/fabric-rendering-v1/build.gradle index 03e7f6a0eec..09428995742 100644 --- a/fabric-rendering-v1/build.gradle +++ b/fabric-rendering-v1/build.gradle @@ -1,18 +1,22 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/client/resources/fabric-rendering-v1.classtweaker') } -moduleDependencies(project, [ - ':fabric-api-base', - ':fabric-transitive-access-wideners-v1', - ':fabric-lifecycle-events-v1' -]) +fabricApiModule { + moduleDependencies( + ':fabric-api-base', + ':fabric-transitive-access-wideners-v1', + ':fabric-lifecycle-events-v1' + ) -testDependencies(project, [ - ':fabric-client-gametest-api-v1', - ':fabric-item-api-v1', - ':fabric-object-builder-api-v1', - ':fabric-screen-api-v1' -]) + testDependencies( + ':fabric-client-gametest-api-v1', + ':fabric-item-api-v1', + ':fabric-object-builder-api-v1', + ':fabric-screen-api-v1' + ) +} diff --git a/fabric-resource-conditions-api-v1/build.gradle b/fabric-resource-conditions-api-v1/build.gradle index 84e239cecca..40533c96919 100644 --- a/fabric-resource-conditions-api-v1/build.gradle +++ b/fabric-resource-conditions-api-v1/build.gradle @@ -1,10 +1,14 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-resource-conditions-api-v1.classtweaker") } -testDependencies(project, [ - ':fabric-lifecycle-events-v1', - ':fabric-resource-loader-v1' -]) +fabricApiModule { + testDependencies( + ':fabric-lifecycle-events-v1', + ':fabric-resource-loader-v1' + ) +} diff --git a/fabric-resource-loader-v1/build.gradle b/fabric-resource-loader-v1/build.gradle index 591c6ea58d1..5a946f266ee 100644 --- a/fabric-resource-loader-v1/build.gradle +++ b/fabric-resource-loader-v1/build.gradle @@ -1,17 +1,23 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file("src/main/resources/fabric-resource-loader-v1.classtweaker") } -moduleDependencies(project, ['fabric-api-base']) - -testDependencies(project, [ - ':fabric-lifecycle-events-v1', - ':fabric-api-base', - ':fabric-gametest-api-v1', - ':fabric-resource-loader-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) + + testDependencies( + ':fabric-lifecycle-events-v1', + ':fabric-api-base', + ':fabric-gametest-api-v1', + ':fabric-resource-loader-v1' + ) +} // Setup 3 test mods used for testing resource sorting sourceSets { diff --git a/fabric-screen-api-v1/build.gradle b/fabric-screen-api-v1/build.gradle index 9ced6ef8b49..05b45fadd08 100644 --- a/fabric-screen-api-v1/build.gradle +++ b/fabric-screen-api-v1/build.gradle @@ -1,3 +1,9 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, ['fabric-api-base']) +fabricApiModule { + moduleDependencies( + 'fabric-api-base' + ) +} diff --git a/fabric-serialization-api-v1/build.gradle b/fabric-serialization-api-v1/build.gradle index 714fb5d4d34..814f72ae199 100644 --- a/fabric-serialization-api-v1/build.gradle +++ b/fabric-serialization-api-v1/build.gradle @@ -1,4 +1,6 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-serialization-api-v1.classtweaker') diff --git a/fabric-sound-api-v1/build.gradle b/fabric-sound-api-v1/build.gradle index ad829bbf8a1..ee4b4f0e0bf 100644 --- a/fabric-sound-api-v1/build.gradle +++ b/fabric-sound-api-v1/build.gradle @@ -1,11 +1,15 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/client/resources/fabric-sound-api-v1.classtweaker') } -testDependencies(project, [ - ':fabric-api-base', - ':fabric-resource-loader-v1', - ':fabric-command-api-v2' -]) +fabricApiModule { + testDependencies( + ':fabric-api-base', + ':fabric-resource-loader-v1', + ':fabric-command-api-v2' + ) +} diff --git a/fabric-tag-api-v1/build.gradle b/fabric-tag-api-v1/build.gradle index ce5741d79ce..d96fbba500a 100644 --- a/fabric-tag-api-v1/build.gradle +++ b/fabric-tag-api-v1/build.gradle @@ -1,17 +1,21 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} loom { accessWidenerPath = file('src/main/resources/fabric-tag-api-v1.classtweaker') } -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-resource-loader-v1' -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-resource-loader-v1' + ) -testDependencies(project, [ - ':fabric-convention-tags-v2', - ':fabric-client-gametest-api-v1', - ':fabric-lifecycle-events-v1', - ':fabric-resource-loader-v1', -]) + testDependencies( + ':fabric-convention-tags-v2', + ':fabric-client-gametest-api-v1', + ':fabric-lifecycle-events-v1', + ':fabric-resource-loader-v1', + ) +} diff --git a/fabric-transfer-api-v1/build.gradle b/fabric-transfer-api-v1/build.gradle index 11be313e00b..148fefc0816 100644 --- a/fabric-transfer-api-v1/build.gradle +++ b/fabric-transfer-api-v1/build.gradle @@ -1,16 +1,20 @@ -version = getSubprojectVersion(project) +plugins { + id 'fabric-api.module' +} -moduleDependencies(project, [ - 'fabric-api-base', - 'fabric-api-lookup-api-v1', - 'fabric-lifecycle-events-v1', - // transitive dependency of API Lookup - 'fabric-rendering-fluids-v1', -]) +fabricApiModule { + moduleDependencies( + 'fabric-api-base', + 'fabric-api-lookup-api-v1', + 'fabric-lifecycle-events-v1', + // transitive dependency of API Lookup + 'fabric-rendering-fluids-v1', + ) -testDependencies(project, [ - ':fabric-object-builder-api-v1', - ':fabric-rendering-v1', - ':fabric-resource-loader-v1', - ':fabric-command-api-v2' -]) + testDependencies( + ':fabric-object-builder-api-v1', + ':fabric-rendering-v1', + ':fabric-resource-loader-v1', + ':fabric-command-api-v2' + ) +} diff --git a/fabric-transitive-access-wideners-v1/build.gradle b/fabric-transitive-access-wideners-v1/build.gradle index 76d5f1e57ba..bda81c18975 100644 --- a/fabric-transitive-access-wideners-v1/build.gradle +++ b/fabric-transitive-access-wideners-v1/build.gradle @@ -1,224 +1,193 @@ -version = getSubprojectVersion(project) +import net.fabricmc.fabric.impl.build.AbstractGenerateClassTweakerTask -loom { - accessWidenerPath = file('src/main/resources/fabric-transitive-access-wideners-v1.classtweaker') -} - -testDependencies(project, [ - ':fabric-rendering-v1', - ':fabric-object-builder-api-v1' -]) - - -import org.objectweb.asm.ClassReader +import org.gradle.api.tasks.TaskAction import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor import org.objectweb.asm.Opcodes import org.objectweb.asm.Type -import org.objectweb.asm.tree.ClassNode -import java.nio.file.FileSystem -import java.nio.file.FileSystems -import java.nio.file.Files -import java.nio.file.Path +plugins { + id 'fabric-api.module' +} -tasks.register('generateClassTweaker') { - doLast { - List lines = new ArrayList<>(); - lines.add("accessWidener v2 official") - lines.add("") - lines.add("# DO NOT EDIT BY HAND! This file is generated automatically.") - lines.add("# Edit \"template.classtweaker\" instead then run \"gradlew generateClassTweaker\".") - lines.add("") - lines.addAll(file("template.classtweaker").text.lines().toList()) - - Path commonJar = loom.namedMinecraftProvider.parentMinecraftProvider.commonJar.path - - FileSystems.newFileSystem(URI.create("jar:${commonJar.toUri()}"), [create: false]).withCloseable { fs -> - generateBlockConstructors(lines, fs) - lines.add("") - generateCreatorEntityTrackedDataMethods(lines, fs) - lines.add("") - generateEnchantmentMethods(lines, fs) - lines.add("") - generateNoiseRouterDataFieldsAndMethods(lines, fs) - lines.add("") - } +loom { + accessWidenerPath = file('src/main/resources/fabric-transitive-access-wideners-v1.classtweaker') +} - Path clientJar = loom.namedMinecraftProvider.parentMinecraftProvider.clientOnlyJar.path +fabricApiModule { + testDependencies( + ':fabric-rendering-v1', + ':fabric-object-builder-api-v1' + ) +} - FileSystems.newFileSystem(URI.create("jar:${clientJar.toUri()}"), [create: false]).withCloseable { fs -> - generateRenderPipelinesFields(lines, fs) - } +abstract class GenerateTransitiveAccessWidenersClassTweakerTask extends AbstractGenerateClassTweakerTask { + @TaskAction + void run() { + List lines = new ArrayList<>() + addHeader(lines, "accessWidener v2 official") - file('src/main/resources/fabric-transitive-access-wideners-v1.classtweaker').text = String.join('\n', lines) + '\n' + generateBlockConstructors(lines) + lines.add("") + generateCreatorEntityTrackedDataMethods(lines) + lines.add("") + generateEnchantmentMethods(lines) + lines.add("") + generateNoiseRouterDataFieldsAndMethods(lines) + lines.add("") + generateRenderPipelinesFields(lines) validateAccessWidener(lines) + writeOutput(lines) } -} -def generateBlockConstructors(List lines, FileSystem fs) { - lines.add("# Constructors of non-abstract block classes") - Files.list(fs.getPath("net/minecraft/world/level/block/")) - .filter { Files.isRegularFile(it) && it.toString().endsWith(".class") } - .map { loadClass(it) } - .sorted(Comparator.comparing { it.name }) - .filter { (it.access & Opcodes.ACC_ABSTRACT) == 0 } - .forEach { node -> - for (def method : node.methods) { - // Checklist for finding block constructors as of 1.19.3: - // - class directly in net.minecraft.block (excluding subpackages) - // - method name == (by definition) - // - contains an AbstractBlock$Settings parameter - // - only taking into account non-abstract classes and non-public constructors - - // Block constructor... - if (method.name == "" && Type.getArgumentTypes(method.desc).any { it.internalName == 'net/minecraft/world/level/block/state/BlockBehaviour$Properties' }) { - // ...and non-public - if ((method.access & Opcodes.ACC_PUBLIC) == 0) { - lines.add("transitive-accessible method $node.name $method.desc") + private void generateBlockConstructors(List lines) { + lines.add("# Constructors of non-abstract block classes") + + readPackageClasses("net/minecraft/world/level/block") + .toSorted { it.name } + .findAll { (it.access & Opcodes.ACC_ABSTRACT) == 0 } + .forEach { node -> + for (def method : node.methods) { + def takesBlockProperties = Type.getArgumentTypes(method.desc).any { + it.internalName == "net/minecraft/world/level/block/state/BlockBehaviour\$Properties" + } + + if (method.name == "" && takesBlockProperties && (method.access & Opcodes.ACC_PUBLIC) == 0) { + lines.add("transitive-accessible method ${node.name} ${method.desc}") } } } - } -} - -def generateRenderPipelinesFields(List lines, FileSystem fs) { - lines.add("# private fields of RenderPipelines") + } - def node = loadClass(fs.getPath("net/minecraft/client/renderer/RenderPipelines.class")) + private void generateRenderPipelinesFields(List lines) { + lines.add("# private fields of RenderPipelines") + def node = readClass("net/minecraft/client/renderer/RenderPipelines") - for (def field : node.fields) { - // All private fields of RenderPipelines of type RenderPipeline.Snippet - if ((field.access & Opcodes.ACC_PRIVATE) != 0 && field.desc == "Lcom/mojang/blaze3d/pipeline/RenderPipeline\$Snippet;") { - lines.add("transitive-accessible field $node.name ${field.name} ${field.desc}") + for (def field : node.fields) { + if ((field.access & Opcodes.ACC_PRIVATE) != 0 && field.desc == "Lcom/mojang/blaze3d/pipeline/RenderPipeline\$Snippet;") { + lines.add("transitive-accessible field ${node.name} ${field.name} ${field.desc}") + } } - } - for (def method : node.methods) { - if ((method.access & Opcodes.ACC_PRIVATE) != 0 && (method.access & Opcodes.ACC_STATIC) != 0) { - lines.add("transitive-accessible method $node.name $method.name $method.desc") + for (def method : node.methods) { + if ((method.access & Opcodes.ACC_PRIVATE) != 0 && (method.access & Opcodes.ACC_STATIC) != 0) { + lines.add("transitive-accessible method ${node.name} ${method.name} ${method.desc}") + } } } -} - -def generateNoiseRouterDataFieldsAndMethods(List lines, FileSystem fs) { - lines.add("# private fields of NoiseRouterData") - def node = loadClass(fs.getPath("net/minecraft/world/level/levelgen/NoiseRouterData.class")) + private void generateNoiseRouterDataFieldsAndMethods(List lines) { + lines.add("# private fields of NoiseRouterData") + def node = readClass("net/minecraft/world/level/levelgen/NoiseRouterData") - for (def field : node.fields) { - // Every field can be useful - if ((field.access & Opcodes.ACC_PRIVATE) != 0) { - lines.add("transitive-accessible field $node.name $field.name $field.desc") + for (def field : node.fields) { + if ((field.access & Opcodes.ACC_PRIVATE) != 0) { + lines.add("transitive-accessible field ${node.name} ${field.name} ${field.desc}") + } } - } - lines.add("# private and protected methods of NoiseRouterData") + lines.add("# private and protected methods of NoiseRouterData") - for (def method : node.methods) { - // Every method can be useful, but using the vanilla namespace is not recommended - if ((method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) && !method.name.startsWith("lambda") && method.name != "createKey") { - lines.add("transitive-accessible method $node.name $method.name $method.desc") + for (def method : node.methods) { + if ((method.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) != 0 && !method.name.startsWith("lambda") && method.name != "createKey") { + lines.add("transitive-accessible method ${node.name} ${method.name} ${method.desc}") + } } } -} -def generateTrackedDataFields(String className, List lines, FileSystem fs, String... extraMethods) { - // using a set to prevent duplicates from multiple dataTracker references in a single method - // linked to preserve order and improve generated access widener readability - Set collectedWideners = new LinkedHashSet<>() + private void generateTrackedDataFields(String className, List lines, String... extraMethods) { + Set collectedWideners = new LinkedHashSet<>() - loadClass(fs.getPath("${className}.class")).accept( - new ClassVisitor(Opcodes.ASM9) { + readClass(className).accept(new ClassVisitor(Opcodes.ASM9) { @Override MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { boolean isExtra = extraMethods.contains(name) - if (!isExtra) { - // check for desired methods - if (!name.startsWith("get") && !name.startsWith("set")) { - return null - } - - // check methods as genuine basic getter/setters - int parameterCount = Type.getArgumentCount(descriptor) - - if (name.startsWith("get") && parameterCount != 0) { - return null - } - if (name.startsWith("set") && parameterCount != 1) { - return null - } - } else { - println name + if (!isExtra && !isTrackedDataAccessor(name, descriptor)) { + return null } return new MethodVisitor(Opcodes.ASM9) { @Override void visitFieldInsn(int opcode, String owner, String fieldName, String fieldDescriptor) { - // check references its dataTracker field - if (isExtra || (fieldName == "entityData" && opcode == Opcodes.GETFIELD)) { - collectedWideners.add("transitive-accessible method $className $name $descriptor") + if (isExtra || fieldName == "entityData" && opcode == Opcodes.GETFIELD) { + collectedWideners.add("transitive-accessible method ${className} ${name} ${descriptor}") } } } } - } - ) - - lines.addAll(collectedWideners) -} + }) -def generateCreatorEntityTrackedDataMethods(List lines, FileSystem fs) { - lines.add("# Private tracked data related methods of DisplayEntity (plus its subclasses) and InteractionEntity") + lines.addAll(collectedWideners) + } - generateTrackedDataFields("net/minecraft/world/entity/Display", lines, fs, "getTransformation") - generateTrackedDataFields("net/minecraft/world/entity/Display\$ItemDisplay", lines, fs) - generateTrackedDataFields("net/minecraft/world/entity/Display\$BlockDisplay", lines, fs) - generateTrackedDataFields("net/minecraft/world/entity/Display\$TextDisplay", lines, fs) - generateTrackedDataFields("net/minecraft/world/entity/Interaction", lines, fs, "shouldRespond") -} + private void generateCreatorEntityTrackedDataMethods(List lines) { + lines.add("# Private tracked data related methods of DisplayEntity (plus its subclasses) and InteractionEntity") + generateTrackedDataFields("net/minecraft/world/entity/Display", lines, "getTransformation") + generateTrackedDataFields("net/minecraft/world/entity/Display\$ItemDisplay", lines) + generateTrackedDataFields("net/minecraft/world/entity/Display\$BlockDisplay", lines) + generateTrackedDataFields("net/minecraft/world/entity/Display\$TextDisplay", lines) + generateTrackedDataFields("net/minecraft/world/entity/Interaction", lines, "shouldRespond") + } -def generateEnchantmentMethods(List lines, FileSystem fs) { - lines.add("# Private methods of Enchantment and EnchantmentHelper") + private void generateEnchantmentMethods(List lines) { + lines.add("# Private methods of Enchantment and EnchantmentHelper") - for (def node : [ loadClass(fs.getPath("net/minecraft/world/item/enchantment/EnchantmentHelper.class")), loadClass(fs.getPath("net/minecraft/world/item/enchantment/Enchantment.class")) ]) { - for (def method : node.methods) { - // All private inner methods of EnchantmentHelper - if ((method.access & Opcodes.ACC_PRIVATE) != 0 && !method.name.startsWith("lambda\$")) { - lines.add("transitive-accessible method $node.name $method.name $method.desc") + for (def node : [ + readClass("net/minecraft/world/item/enchantment/EnchantmentHelper"), + readClass("net/minecraft/world/item/enchantment/Enchantment") + ]) { + for (def method : node.methods) { + if ((method.access & Opcodes.ACC_PRIVATE) != 0 && !method.name.startsWith("lambda\$")) { + lines.add("transitive-accessible method ${node.name} ${method.name} ${method.desc}") + } } } + + lines.add("transitive-accessible class net/minecraft/world/item/enchantment/EnchantmentHelper\$EnchantmentVisitor") + lines.add("transitive-accessible class net/minecraft/world/item/enchantment/EnchantmentHelper\$EnchantmentInSlotVisitor") + lines.add("transitive-accessible class net/minecraft/world/item/enchantment/Enchantment\$GenericAction") + lines.add("transitive-accessible class net/minecraft/world/item/enchantment/Enchantment\$FloatAction") } - lines.add('transitive-accessible class net/minecraft/world/item/enchantment/EnchantmentHelper$EnchantmentVisitor') - lines.add('transitive-accessible class net/minecraft/world/item/enchantment/EnchantmentHelper$EnchantmentInSlotVisitor') - lines.add('transitive-accessible class net/minecraft/world/item/enchantment/Enchantment$GenericAction') - lines.add('transitive-accessible class net/minecraft/world/item/enchantment/Enchantment$FloatAction') -} -ClassNode loadClass(Path path) { - def node = new ClassNode() + private static boolean isTrackedDataAccessor(String name, String descriptor) { + int parameterCount = Type.getArgumentCount(descriptor) + + if (name.startsWith("get")) { + return parameterCount == 0 + } + + if (name.startsWith("set")) { + return parameterCount == 1 + } - Files.newInputStream(path).withCloseable { is -> - new ClassReader(is).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) + return false } - return node -} + private static void validateAccessWidener(List lines) { + List exceptions = new ArrayList<>() + + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i) -def validateAccessWidener(List lines) { - List exceptions = new ArrayList<>() + if (line.isBlank() || line.startsWith("#") || line.startsWith("transitive-") || line.startsWith("accessWidener")) { + continue + } - for (int i = 0; i < lines.size(); i++) { - String line = lines.get(i) - if (line.isBlank() || line.startsWith("#") || line.startsWith("transitive-") || line.startsWith("accessWidener")) continue exceptions.add(String.valueOf(i + 1)) - } + } - if (exceptions.size() > 0) { - throw new InvalidUserDataException("\"fabric-transitive-access-wideners-v1.classtweaker\" contains non-transitive access modifiers at lines: [" + String.join(", ", exceptions) + "]") + if (!exceptions.isEmpty()) { + throw new RuntimeException("\"fabric-transitive-access-wideners-v1.classtweaker\" contains non-transitive access modifiers at lines: [${String.join(", ", exceptions)}]") + } } } +tasks.register('generateClassTweaker', GenerateTransitiveAccessWidenersClassTweakerTask) { + template = file("template.classtweaker") + outputFile = file("src/main/resources/fabric-transitive-access-wideners-v1.classtweaker") +} + generateResources.dependsOn generateClassTweaker +processResources.mustRunAfter generateClassTweaker diff --git a/gradle.properties b/gradle.properties index cb2c644e730..1097eebbd49 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,9 @@ org.gradle.jvmargs=-Xmx1024M org.gradle.parallel=true -org.gradle.configuration-cache=false +org.gradle.configuration-cache=true version=0.150.3 minecraft_version=26.2-pre-4 -loader_version=0.18.4 -installer_version=1.0.1 prerelease=true curseforge_minecraft_versions=26.1-snapshot diff --git a/gradle/Focus.java b/gradle/Focus.java index 294ad5b983f..9a178fbc07c 100644 --- a/gradle/Focus.java +++ b/gradle/Focus.java @@ -35,7 +35,7 @@ */ public class Focus { // Matches the content of moduleDependencies and testDependencies - private static final Pattern OUTER_PATTERN = Pattern.compile("(?:moduleDependencies|testDependencies)\\s*\\(.*?\\[\\s*([\\s\\S]*?)\\s*\\]\\s*\\)\n"); + private static final Pattern OUTER_PATTERN = Pattern.compile("(?:moduleDependencies|testDependencies)\\s*\\(\\s*([\\s\\S]*?)\\s*\\)"); // Matches the dependency string private static final Pattern INNER_PATTERN = Pattern.compile("['\\\"]([^'\\\"]+)['\\\"]"); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000000..12ed3d9ae8b --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,20 @@ +[versions] +fabric-loader = "0.18.4" +fabric-installer = "1.0.1" +fabric-loom = "1.17.0-alpha.19" +spotless = "8.6.0" +remote-sign = "0.5.0" +mod-publish = "2.0.0" +checkstyle = "13.5.0" +mockito = "5.23.0" + +[libraries] +fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" } +fabric-loader-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" } +fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" } +mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } + +[plugins] +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +remote-sign = { id = "me.modmuss50.remotesign", version.ref = "remote-sign" } +mod-publish = { id = "me.modmuss50.mod-publish-plugin", version.ref = "mod-publish" } diff --git a/gradle/module-validation.gradle b/gradle/module-validation.gradle deleted file mode 100644 index ae3fef8ce1b..00000000000 --- a/gradle/module-validation.gradle +++ /dev/null @@ -1,104 +0,0 @@ -import groovy.json.JsonSlurper - -/* - * This buildscript contains tasks related to the validation of each module in fabric api. - * - * Right now this task verifies each Fabric API module has a module lifecycle specified. - * More functionality will probably be added in the future. - */ - -subprojects { - if (it.name == "deprecated" || it.name == "fabric-api-bom" || it.name == "fabric-api-catalog") { - return - } - - // Create the task - def validateModules = tasks.register("validateModules", ValidateModuleTask) - tasks.check.dependsOn(validateModules) -} - -/** - * Verifies that each module has the required custom values for module lifecycle in it's FMJ. - * - *

Example: - *

{@code
- * "custom": {
- *   "fabric-api:module-lifecycle": "stable"
- * }
- * }
- */ -abstract class ValidateModuleTask extends DefaultTask { - @InputFile - abstract RegularFileProperty getFmj() - - @Input - abstract Property getProjectName() - - @Input - abstract Property getProjectPath() - - @Input - abstract Property getLoaderVersion() - - ValidateModuleTask() { - group = "verification" - - // No outputs - outputs.upToDateWhen { true } - - def file = project.file("src/main/resources/fabric.mod.json") - - if (!file.exists()) { - file = project.file("src/client/resources/fabric.mod.json") - } - - fmj.set(file) - - projectName.set(project.name) - projectPath.set(project.path) - loaderVersion.set(project.loader_version) - } - - @TaskAction - void validate() { - def file = fmj.get().asFile - - def json = new JsonSlurper().parse(file) - - if (json.custom == null) { - throw new GradleException("Module ${projectName.get()} does not have a custom value containing module lifecycle!") - } - - def moduleLifecycle = json.custom.get("fabric-api:module-lifecycle") - - if (moduleLifecycle == null) { - throw new GradleException("Module ${projectName.get()} does not have module lifecycle in custom values!") - } - - if (!moduleLifecycle instanceof String) { - throw new GradleException("Module ${projectName.get()} has an invalid module lifecycle value. The value must be a string but read a ${moduleLifecycle.class}") - } - - // Validate the lifecycle value - switch (moduleLifecycle) { - case "stable": - case "experimental": - break - case "deprecated": - if (!projectPath.get().startsWith(":deprecated")) { - throw new GradleException("Deprecated module ${projectName.get()} must be in the deprecated sub directory.") - } - break - default: - throw new GradleException("Module ${projectName.get()} has an invalid module lifecycle ${json.custom.get('fabric-api:module-lifecycle')}") - } - - if (json.depends == null) { - throw new GradleException("Module ${projectName.get()} does not have a depends value!") - } - - if (json.depends.fabricloader != ">=${loaderVersion.get()}") { - throw new GradleException("Module ${projectName.get()} does not have a valid fabricloader value! Got \"${json.depends.fabricloader}\" but expected \">=${project.loader_version}\"") - } - } -} diff --git a/gradle/module-versioning.gradle b/gradle/module-versioning.gradle deleted file mode 100644 index ed88948bba8..00000000000 --- a/gradle/module-versioning.gradle +++ /dev/null @@ -1,125 +0,0 @@ - -/** - * This task should be used to easily bump the major/minor/patch version of a fabric-api module. - * It will automatically bump the versions of dependent modules. - */ -tasks.register('bumpVersions', BumpVersionTask) - -class BumpVersionTask extends DefaultTask { - BumpVersionTask() { - group = "publishing" - - outputs.upToDateWhen { false } - } - - @TaskAction - void runTask() { - def scanner = new Scanner(System.in) - - def toUpdate = [:] - - while (true) { - println "Enter module name to update, or done to continue" - - def input = scanner.nextLine() - - if (input == "done") { - break - } - - // Bump all versions. To be used when buildscript changes are made. - if (input == "allPatch") { - project.getChildProjects().values().forEach { - if (it.name == "deprecated" || it.name == "fabric-api-bom" || it.name == "fabric-api-catalog") { - return - } - - toUpdate.put(it, 2) - } - - break - } - - def subProject = project.childProjects[input] ?: project.childProjects["deprecated"].childProjects[input] - - if (!subProject) { - println "Could not find project with name: $input" - continue - } - - while (true) { - println "Bump version for ${subProject.name}:" - println "0) Bump Major" - println "1) Bump Minor" - println "2) Bump Patch" - - input = scanner.nextLine() - - if (!(input in ["0", "1", "2"])) { - println "Invalid input" - continue - } - - toUpdate.put(subProject, input as Integer) - break - } - } - - while (true) { - def temp = [:] - - toUpdate.keySet().forEach { p -> - project.allprojects.each { cp -> - if (cp.name == "deprecated" || cp.name == "fabric-api" || cp.name == "fabric-api-bom" || cp.name == "fabric-api-catalog") { - return - } - - def config = cp.configurations.api - config.allDependencies.forEach { dep -> - if (dep.name == p.name) { - if (!toUpdate.containsKey(cp)) { - println "Bumping patch of ${cp.name} as it depends on ${p.name}" - - temp.put(cp, 2) // Bump patch - } - } - } - } - } - - if (temp.isEmpty()) { - break - } - - toUpdate.putAll(temp) - } - - def gpFile = project.file("gradle.properties") - def props = project.properties - def text = gpFile.text - - toUpdate.forEach { p, i -> - def version = props."${p.name}-version" - - if (!version) { - throw new NullPointerException("Could not find version for " + p.name) - } - - def split = version.split("\\.") - split[i] = (split[i] as Integer) + 1 - for (j in (i + 1) ..< split.length) { - split[j] = 0 - } - def newVersion = split.join(".") - - println "${p.name}: $version -> $newVersion" - - text = text.replace( - "${p.name}-version=$version", - "${p.name}-version=$newVersion" - ) - } - - gpFile.text = text - } -} diff --git a/gradle/package-info.gradle b/gradle/package-info.gradle deleted file mode 100644 index 87192cfe571..00000000000 --- a/gradle/package-info.gradle +++ /dev/null @@ -1,115 +0,0 @@ -import java.nio.file.Files - -for (def sourceSet in [ - sourceSets.main, - sourceSets.client - ]) { - // We have to capture the source set name for the lazy string literals, - // otherwise it'll just be whatever the last source set is in the list. - def sourceSetName = sourceSet.name - def taskName = sourceSet.getTaskName('generate', 'PackageInfos') - def task = tasks.register(taskName, GeneratePackageInfos) { - group = 'fabric' - description = "Generates package-info files for $sourceSetName packages." - - // Only apply to default source directory since we also add the generated - // sources to the source set. - sourceRoot = file("src/$sourceSetName/java") - header = rootProject.file('HEADER') - outputDir = file("src/generated/$sourceSetName") - } - sourceSet.java.srcDir task - - def cleanTask = tasks.register(sourceSet.getTaskName('clean', 'PackageInfos'), Delete) { - group = 'fabric' - delete file("src/generated/$sourceSetName") - } - clean.dependsOn cleanTask -} - -abstract class GeneratePackageInfos extends DefaultTask { - @InputFile - File header - - @Input - abstract Property getProjectName() - - @SkipWhenEmpty - @InputDirectory - final DirectoryProperty sourceRoot = project.objects.directoryProperty() - - @OutputDirectory - final DirectoryProperty outputDir = project.objects.directoryProperty() - - GeneratePackageInfos() { - projectName.set(project.name) - } - - @TaskAction - def run() { - def output = outputDir.get().asFile.toPath() - output.deleteDir() - def headerText = header.readLines().join("\n") // normalize line endings - def root = sourceRoot.get().asFile.toPath() - - root.eachDirRecurse { - def containsJava = Files.list(it).any { - Files.isRegularFile(it) && it.fileName.toString().endsWith('.java') - } - - if (!containsJava) { - return - } - - // Check existing package-info.java to ensure it has @NullMarked - def existingPackageInfo = it.resolve('package-info.java') - if (Files.exists(existingPackageInfo)) { - if (!existingPackageInfo.text.contains("@NullMarked")) { - throw new RuntimeException("package-info.java ${existingPackageInfo} is missing @NullMarked annotation.") - } - - return - } - - def relativePath = root.relativize(it) - def target = output.resolve(relativePath) - Files.createDirectories(target) - - def packageName = relativePath.toString().replace(File.separator, '.') - - if (packageName == "net.fabricmc.fabric.api.util" && projectName.get() == "fabric-content-registries-v0") { - // Hack: This package clashes with api-base, don't generate any annotations for it. - return - } - - def implPattern = /^(net[\/\\]fabricmc[\/\\]fabric[\/\\](impl|mixin))/ - def isImpl = relativePath.toString() =~ implPattern - - target.resolve('package-info.java').withWriter { - if (isImpl) { - it.write("""$headerText - |/** - | * Implementation code for ${projectName.get()}. - | */ - |@ApiStatus.Internal - |@NullMarked - |package $packageName; - | - |import org.jetbrains.annotations.ApiStatus; - |import org.jspecify.annotations.NullMarked; - |""".stripMargin()) - } else { - it.write("""$headerText - |/** - | * API code for ${projectName.get()}. - | */ - |@NullMarked - |package $packageName; - | - |import org.jspecify.annotations.NullMarked; - |""".stripMargin()) - } - } - } - } -} diff --git a/gradle/validate-annotations.gradle b/gradle/validate-annotations.gradle deleted file mode 100644 index 4ab352a4018..00000000000 --- a/gradle/validate-annotations.gradle +++ /dev/null @@ -1,49 +0,0 @@ -tasks.register('validateAnnotations', ValidateAnnotations) { - group = 'fabric' - description = "Validate annotations used in Fabric API code." - - outputs.upToDateWhen { true } // Task has no outputs - - // Only apply to default source directories since there's also generated package-info files. - source file("src/client/java") - source file("src/main/java") - source file("src/testmod/java") - source file("src/testmodClient/java") -} - -tasks.check.dependsOn "validateAnnotations" - -abstract class ValidateAnnotations extends SourceTask { - private static final def API_STATUS_INTERNAL = ~/@ApiStatus\.Internal/ - private static final def ENVIRONMENT = ~/@Environment/ - - @TaskAction - def run() { - for (def dir in [ - 'api', - 'impl', - 'mixin', - 'test' - ]) { - getSource().matching { include "net/fabricmc/fabric/$dir/" }.forEach { - if (it.isDirectory()) { - return - } - - def contents = it.text - - // @Environment is never allowed - if (ENVIRONMENT.matcher(contents).find()) { - throw new RuntimeException("Found @Environment annotation in file: $it") - } - - // @ApiStatus.Internal is only allowed in api packages (it's auto-generated for impl and mixin packages) - if (dir != "api") { - if (API_STATUS_INTERNAL.matcher(contents).find()) { - throw new RuntimeException("Found @ApiStatus.Internal in implementation file: " + it) - } - } - } - } - } -}