version = getSubprojectVersion(project) moduleDependencies(project, [ 'fabric-api-base', 'fabric-convention-tags-v2', 'fabric-registry-sync-v0', 'fabric-networking-api-v1', 'fabric-resource-conditions-api-v1', 'fabric-item-group-api-v1', 'fabric-recipe-api-v1' ]) dependencies { } sourceSets { testmod { resources { srcDirs += [ 'src/testmod/generated' ] } } } loom { accessWidenerPath = file("src/main/resources/fabric-data-generation-api-v1.accesswidener") runs { datagen { inherit testmodServer name "Data Generation" vmArg "-Dfabric-api.datagen" vmArg "-Dfabric-api.datagen.output-dir=${file("src/testmod/generated")}" vmArg "-Dfabric-api.datagen.strict-validation" ideConfigGenerated = true runDir "build/datagen" } datagenClient { client() name "Data Generation Client" vmArg "-Dfabric-api.datagen" vmArg "-Dfabric-api.datagen.output-dir=${file("src/testmod/generated")}" vmArg "-Dfabric-api.datagen.strict-validation" ideConfigGenerated = true runDir "build/datagen" source sourceSets.testmodClient } } } test.dependsOn runDatagenClient tasks.register('datapackZip', Zip) { dependsOn runDatagen archiveFileName = "${base.archivesName.get()}-${project.version}-test-datapack.zip" destinationDirectory = layout.buildDirectory.dir('libs') from file("src/testmod/generated") from file("pack.mcmeta") } build.dependsOn datapackZip import org.objectweb.asm.ClassReader import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode import java.lang.reflect.Modifier import java.util.zip.ZipEntry import java.util.zip.ZipFile tasks.register('generateAccessWidener') { 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 accessWidener = "accessWidener\tv2\tnamed\n" accessWidener += "\n" accessWidener += "# DO NOT EDIT BY HAND! This file is generated automatically.\n" accessWidener += "# Edit \"template.accesswidener\" instead then run \"gradlew generateAccessWidener\".\n" accessWidener += "\n" accessWidener += file("template.accesswidener").text + "\n" visitMethods(classes["net/minecraft/data/server/recipe/RecipeGenerator"]) { name, desc, owner -> if (it.name == "generate") return accessWidener += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" } visitMethods(classes["net/minecraft/data/client/BlockStateModelGenerator"]) { name, desc, owner -> if (desc == "()V") // Skip over methods that dont take any arguments, as they are specific to minecraft. return accessWidener += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" } visitMethods(classes["net/minecraft/data/server/loottable/BlockLootTableGenerator"]) { name, desc, owner -> accessWidener += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" } visitMethods(classes["net/minecraft/data/client/ItemModelGenerator"]) { name, desc, owner -> accessWidener += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n" } classes.values().forEach { classNode -> visitFinalMethods(classNode) { name, desc, owner -> if (name != "getName" || desc != "()Ljava/lang/String;") { // Not the method we are after return } if (!hasAncestor(classNode, classes, "net/minecraft/data/DataProvider")) { // Not a descendant of DataProvider return } accessWidener += "transitive-extendable\tmethod\t${owner}\t${name}\t${desc}\n" } } file("src/main/resources/fabric-data-generation-api-v1.accesswidener").text = accessWidener } } 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) } } static def visitFinalMethods(ClassNode classNode, closure) { classNode.methods.forEach { if (!Modifier.isFinal(it.access)) return if (it.name.startsWith("<")) return closure(it.name, it.desc, 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) classes.put(classNode.name, classNode) } } } } return classes } def 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 } for (def interfaceName : classNode.interfaces) { if (interfaceName == ancestorName) { return true } def ifaceClass = classes.get(interfaceName) if (ifaceClass != null && hasAncestor(ifaceClass, classes, ancestorName)) { return true } } } generateResources.dependsOn generateAccessWidener generateResources.dependsOn runDatagenClient