Skip to content

Commit 90a4a59

Browse files
committed
fix(api): Fix ComponentBuilder#build ABI compatibility
1 parent b52e823 commit 90a4a59

4 files changed

Lines changed: 96 additions & 0 deletions

File tree

api/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
plugins {
22
id("adventure.common-conventions")
3+
id("adventure.legacy-component-builder-abi-fix")
34
alias(libs.plugins.jmh)
45
}
56

build-logic/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55
}
66

77
dependencies {
8+
implementation(libs.build.asm)
89
implementation(libs.build.errorpronePlugin)
910
implementation(libs.build.indra)
1011
implementation(libs.build.indra.crossdoc)
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import org.gradle.api.DefaultTask
2+
import org.gradle.api.file.RegularFileProperty
3+
import org.gradle.api.tasks.InputFile
4+
import org.gradle.api.tasks.OutputFile
5+
import org.gradle.api.tasks.TaskAction
6+
import org.gradle.api.tasks.compile.JavaCompile
7+
import org.gradle.jvm.tasks.Jar
8+
import org.objectweb.asm.ClassReader
9+
import org.objectweb.asm.ClassVisitor
10+
import org.objectweb.asm.ClassWriter
11+
import org.objectweb.asm.MethodVisitor
12+
import org.objectweb.asm.Opcodes
13+
14+
// Remove in 6.x.
15+
// In 5.x, ComponentBuilder#build erased from Object to Component, causing NoSuchMethodError for 4.x-compiled clients.
16+
// This adds a build-time classfile bridge for build():Object while keeping the 5.x source API strongly typed.
17+
18+
abstract class PatchComponentBuilderAbi : DefaultTask() {
19+
@get:InputFile
20+
abstract val inputClass: RegularFileProperty
21+
22+
@get:OutputFile
23+
abstract val outputClass: RegularFileProperty
24+
25+
@TaskAction
26+
fun patch() {
27+
val reader = ClassReader(inputClass.get().asFile.readBytes())
28+
val writer = ClassWriter(reader, 0)
29+
30+
var hasComponentBuild = false
31+
var hasObjectBuild = false
32+
33+
reader.accept(object : ClassVisitor(Opcodes.ASM9, writer) {
34+
override fun visitMethod(access: Int, name: String, desc: String, sig: String?, ex: Array<out String>?): MethodVisitor {
35+
if (name == "build" && desc == "()Lnet/kyori/adventure/text/Component;") hasComponentBuild = true
36+
if (name == "build" && desc == "()Ljava/lang/Object;") hasObjectBuild = true
37+
return super.visitMethod(access, name, desc, sig, ex)
38+
}
39+
40+
override fun visitEnd() {
41+
check(hasComponentBuild) { "Missing ComponentBuilder.build():Component" }
42+
43+
if (!hasObjectBuild) {
44+
super.visitMethod(
45+
Opcodes.ACC_PUBLIC or Opcodes.ACC_BRIDGE or Opcodes.ACC_SYNTHETIC,
46+
"build",
47+
"()Ljava/lang/Object;",
48+
null,
49+
null
50+
).apply {
51+
visitCode()
52+
visitVarInsn(Opcodes.ALOAD, 0)
53+
visitMethodInsn(
54+
Opcodes.INVOKEINTERFACE,
55+
"net/kyori/adventure/text/ComponentBuilder",
56+
"build",
57+
"()Lnet/kyori/adventure/text/Component;",
58+
true
59+
)
60+
visitInsn(Opcodes.ARETURN)
61+
visitMaxs(1, 1)
62+
visitEnd()
63+
}
64+
}
65+
66+
super.visitEnd()
67+
}
68+
}, 0)
69+
70+
outputClass.get().asFile.apply {
71+
parentFile.mkdirs()
72+
writeBytes(writer.toByteArray())
73+
}
74+
}
75+
}
76+
77+
val componentBuilderClass = "net/kyori/adventure/text/ComponentBuilder.class"
78+
val patchedClasses = layout.buildDirectory.dir("patched-component-builder-abi")
79+
80+
val patchComponentBuilderAbi = tasks.register<PatchComponentBuilderAbi>("patchComponentBuilderAbi") {
81+
val compileJava = tasks.named<JavaCompile>("compileJava")
82+
inputClass.set(compileJava.flatMap { it.destinationDirectory.file(componentBuilderClass) })
83+
outputClass.set(patchedClasses.map { it.file(componentBuilderClass) })
84+
dependsOn(compileJava)
85+
}
86+
87+
tasks.named<Jar>("jar") {
88+
dependsOn(patchComponentBuilderAbi)
89+
exclude(componentBuilderClass)
90+
from(patchedClasses) {
91+
include(componentBuilderClass)
92+
}
93+
}

gradle/libs.versions.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ contractValidator = "ca.stellardrift:contract-validator:1.0.1"
5252
errorprone = { module = "com.google.errorprone:error_prone_core", version.ref = "errorprone" }
5353
stylecheck = "ca.stellardrift:stylecheck:0.2.1"
5454

55+
build-asm = { module = "org.ow2.asm:asm", version = "9.9.1" }
5556
build-errorpronePlugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version = "5.0.0" }
5657
build-indra = { module = "net.kyori:indra-common", version.ref = "indra" }
5758
build-indra-crossdoc = { module = "net.kyori:indra-crossdoc", version.ref = "indra" }

0 commit comments

Comments
 (0)