diff --git a/build.gradle b/build.gradle index 6026ee71ed..342f87df76 100644 --- a/build.gradle +++ b/build.gradle @@ -1200,19 +1200,26 @@ project(':forge') { */ } } - + task findFinalizeSpawnTargets(type: BytecodePredicateFinder, dependsOn: ['createJoinedSRG']) { jar = createJoinedSRG.output output = rootProject.file('src/main/resources/coremods/finalize_spawn_targets.json') predicate = { - parent, node, insn -> + parent, node, insn -> 'net/minecraft/world/level/BaseSpawner' != parent.name // Ignore this class as we special case it. - && insn.getOpcode() == Opcodes.INVOKEVIRTUAL - && insn.name.equals('m_6518_') + && insn.getOpcode() == Opcodes.INVOKEVIRTUAL + && insn.name.equals('m_6518_') && insn.desc.equals('(Lnet/minecraft/world/level/ServerLevelAccessor;Lnet/minecraft/world/DifficultyInstance;Lnet/minecraft/world/entity/MobSpawnType;Lnet/minecraft/world/entity/SpawnGroupData;Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/world/entity/SpawnGroupData;') } } + tasks.register('validateDeprecations', ValidateDeprecations) { + input = tasks.jar.archiveFile + mcVersion = MC_VERSION + } + + tasks.jar.finalizedBy 'validateDeprecations' + tasks.register('checkAll') { dependsOn 'checkLicenses' group = 'checks' diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy index d8555f716b..2d5881dc21 100644 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy +++ b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/BytecodeFinder.groovy @@ -6,15 +6,10 @@ import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction -import org.objectweb.asm.ClassReader -import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldNode import org.objectweb.asm.tree.MethodNode -import java.util.zip.ZipEntry -import java.util.zip.ZipInputStream - abstract class BytecodeFinder extends DefaultTask { @InputFile abstract RegularFileProperty getJar() @OutputFile abstract RegularFileProperty getOutput() @@ -33,18 +28,7 @@ abstract class BytecodeFinder extends DefaultTask { pre() - jar.get().asFile.withInputStream { i -> - new ZipInputStream(i).withCloseable { zin -> - ZipEntry zein - while ((zein = zin.nextEntry) != null) { - if (zein.name.endsWith('.class')) { - def node = new ClassNode(Opcodes.ASM9) - new ClassReader(zin).accept(node, 0) - process(node) - } - } - } - } + Util.processClassNodes(jar.get().asFile, this::process) post() outputFile.text = new JsonBuilder(getData()).toPrettyString() diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy index 91e196d25e..d40fbee418 100644 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy +++ b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/InstallerJar.groovy @@ -6,7 +6,7 @@ import org.gradle.api.provider.SetProperty abstract class InstallerJar extends Zip { @Input @Optional abstract SetProperty getPackedDependencies() - + InstallerJar() { archiveClassifier.set('installer') archiveExtension.set('jar') // Needs to be Zip task to not override Manifest, so set extension @@ -15,7 +15,7 @@ abstract class InstallerJar extends Zip { def installerJson = project.tasks.installerJson def launcherJson = project.tasks.launcherJson def downloadInstaller = project.tasks.downloadInstaller - + dependsOn(installerJson, launcherJson, downloadInstaller) from(installerJson, launcherJson) @@ -24,13 +24,13 @@ abstract class InstallerJar extends Zip { from(project.zipTree(downloadInstaller.output)) { duplicatesStrategy = 'exclude' } - - if (!System.env.MAVEN_USER) { + + if (!System.env.TEAMCITY_VERSION) { dependsOn(project.universalJar) from(project.universalJar) { into '/maven/' + project.group.replace('.', '/') + '/' + project.universalJar.archiveBaseName.get() + '/' + project.version + '/' } - + packedDependencies.get().forEach { def jarTask = project.rootProject.getTasks().findByPath(it) dependsOn(jarTask) diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy index 76b171b7d1..791d5d1f08 100644 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy +++ b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/Util.groovy @@ -2,13 +2,20 @@ package net.minecraftforge.forge.tasks import groovy.json.JsonBuilder import groovy.json.JsonSlurper +import org.objectweb.asm.ClassReader +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode import java.io.File import java.security.MessageDigest import java.text.SimpleDateFormat import java.util.Date +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream public class Util { + static final ASM_LEVEL = Opcodes.ASM9 + static void init() { File.metaClass.sha1 = { -> MessageDigest md = MessageDigest.getInstance('SHA-1') @@ -147,4 +154,19 @@ public class Util { def json = new JsonSlurper().parseText(new URL("https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json").getText("UTF-8")) return json.promos["$mcVersion-latest"] } + + static void processClassNodes(File file, Closure process) { + file.withInputStream { i -> + new ZipInputStream(i).withCloseable { zin -> + ZipEntry zein + while ((zein = zin.nextEntry) != null) { + if (zein.name.endsWith('.class')) { + def node = new ClassNode(ASM_LEVEL) + new ClassReader(zin).accept(node, 0) + process(node) + } + } + } + } + } } \ No newline at end of file diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy new file mode 100644 index 0000000000..90738544df --- /dev/null +++ b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/ValidateDeprecations.groovy @@ -0,0 +1,92 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.forge.tasks + +import net.minecraftforge.srgutils.MinecraftVersion +import org.gradle.api.DefaultTask +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 +import org.objectweb.asm.tree.AnnotationNode +import org.objectweb.asm.tree.ClassNode + +abstract class ValidateDeprecations extends DefaultTask { + @InputFile + abstract RegularFileProperty getInput() + + @Input + abstract Property getMcVersion() + + ValidateDeprecations() { + this.onlyIf { !System.env.TEAMCITY_VERSION } + } + + @TaskAction + protected void exec() { + def mcVer = MinecraftVersion.from(mcVersion.get()) + def errors = [] + + Util.processClassNodes(input.get().asFile) { + processNode(mcVer, errors, it) + } + + if (!errors.isEmpty()) { + errors.forEach { + this.logger.error("Deprecated ${it[0]} is marked for removal in ${it[1]} but is not yet removed") + } + throw new IllegalStateException("Found deprecated members marked for removal but not yet removed in ${mcVer}; see log for details") + } + } + + protected processNode(MinecraftVersion mcVer, List errors, ClassNode node) { + node.visibleAnnotations?.each { annotation -> + ValidateDeprecations.processAnnotations(annotation, mcVer, errors) { + "class ${node.name}" + } + } + node.fields?.each { field -> + field.visibleAnnotations?.each { annotation -> + ValidateDeprecations.processAnnotations(annotation, mcVer, errors) { + "field ${node.name}#${field.name}" + } + } + } + node.methods?.each { method -> + method.visibleAnnotations?.each { annotation -> + ValidateDeprecations.processAnnotations(annotation, mcVer, errors) { + "method ${node.name}#${method.name}${method.desc}" + } + } + } + } + + private static void processAnnotations(AnnotationNode annotation, MinecraftVersion mcVer, List errors, Closure context) { + def values = annotation.values + if (values === null) + return + int forRemoval = values.indexOf('forRemoval') + int since = values.indexOf('since') + if (annotation.desc == 'Ljava/lang/Deprecated;' && forRemoval !== -1 && since !== -1 && values.size() >= 4 && values[forRemoval + 1] === true) { + def oldVersion = MinecraftVersion.from(values[since + 1]) + def split = ValidateDeprecations.splitDots(oldVersion.toString()) + if (split.length < 2) + return + def removeVersion = MinecraftVersion.from("${split[0]}.${split[1] + 1}") + if (removeVersion <= mcVer) + errors.add([context(), removeVersion]) + } + } + + private static int[] splitDots(String version) { + String[] pts = version.split('\\.') + int[] values = new int[pts.length] + for (int x = 0; x < pts.length; x++) + values[x] = Integer.parseInt(pts[x]) + return values + } +} diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy index 254596bdd8..f3d6db46a7 100644 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy +++ b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckExcs.groovy @@ -1,5 +1,6 @@ package net.minecraftforge.forge.tasks.checks +import net.minecraftforge.forge.tasks.Util import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.InputFile @@ -7,7 +8,6 @@ import org.gradle.api.tasks.InputFiles import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import java.util.zip.ZipEntry @@ -67,7 +67,7 @@ abstract class CheckExcs extends CheckTask { private void collectKnown(Collection known) { binary.get().asFile.withInputStream { i -> new ZipInputStream(i).withCloseable { zin -> - final visitor = new ClassVisitor(Opcodes.ASM9) { + final visitor = new ClassVisitor(Util.ASM_LEVEL) { private String cls @Override void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {