fabric/fabric-data-generation-api-v1/build.gradle
2024-11-13 18:31:05 +00:00

208 lines
5.5 KiB
Groovy

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/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/client/data/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/loottable/BlockLootTableGenerator"]) { name, desc, owner ->
accessWidener += "transitive-accessible\tmethod\t${owner}\t${name}\t${desc}\n"
}
visitMethods(classes["net/minecraft/client/data/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<File> inputs) {
Map<String, ClassNode> 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<String, ClassNode> 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