From b6fcbc69b8cde969ac42aa9c05435a047f360c19 Mon Sep 17 00:00:00 2001 From: zhibei <785740487@qq.com> Date: Tue, 14 Jul 2026 05:20:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(build):=20=E4=BF=AE=E5=A4=8D=20Maven=20?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=A8=A1=E5=9E=8B=E4=B8=8E=20Java=208=20?= =?UTF-8?q?=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 249 ++++++++++++++++-- common-legacy-api/build.gradle.kts | 10 +- .../java/taboolib/common/ClassAppender.java | 24 +- .../taboolib/common/ClassAppenderTest.java | 14 + module/database/build.gradle.kts | 13 +- .../database-ptc-object/build.gradle.kts | 12 +- .../module/incision/weaver/SiteWeaver.kt | 2 +- .../kether/action/transform/ActionArray.kt | 2 +- 8 files changed, 283 insertions(+), 43 deletions(-) create mode 100644 common/src/test/java/taboolib/common/ClassAppenderTest.java diff --git a/build.gradle.kts b/build.gradle.kts index e3a8f15e3..b44912e2a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,11 +1,22 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.publish.maven.tasks.GenerateMavenPom +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.compile.JavaCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import ru.vyarus.gradle.plugin.animalsniffer.AnimalSnifferExtension +import java.io.DataInputStream +import java.io.File +import javax.xml.parsers.DocumentBuilderFactory plugins { `maven-publish` java id("org.jetbrains.kotlin.jvm") version "1.8.22" apply false id("com.github.johnrengelman.shadow") version "7.1.2" apply false + id("ru.vyarus.animalsniffer") version "2.0.1" apply false } subprojects { @@ -13,6 +24,7 @@ subprojects { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "com.github.johnrengelman.shadow") apply(plugin = "maven-publish") + apply(plugin = "ru.vyarus.animalsniffer") repositories { maven("https://jitpack.io") @@ -33,6 +45,7 @@ subprojects { compileOnly("org.apache.commons:commons-lang3:3.5") compileOnly("org.tabooproject.reflex:reflex:1.2.4") compileOnly("org.tabooproject.reflex:analyser:1.2.4") + add("signature", "org.codehaus.mojo.signature:java18:1.0@signature") // 测试依赖 testImplementation(kotlin("stdlib")) testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") @@ -49,6 +62,27 @@ subprojects { withSourcesJar() } + configure { + ignore = listOf( + "java.lang.invoke.MethodHandle", + "co.*", + "com.*", + "dev.*", + "ink.*", + "io.*", + "it.*", + "kotlin.*", + "kotlinx.*", + "me.*", + "net.*", + "org.*", + "reactor.*", + "redis.*", + "taboolib.*", + ) + excludeJars = listOf("v260100-260100-minimize") + } + tasks.withType { useJUnitPlatform() } @@ -74,12 +108,17 @@ subprojects { relocate("org.tabooproject", "taboolib.library") } + tasks.named("jar") { + archiveClassifier.set("plain") + } + tasks.build { dependsOn("shadowJar") } tasks.withType { options.encoding = "UTF-8" + options.release.set(8) options.compilerArgs.addAll(listOf("-XDenableSunApiLintControl")) } @@ -100,11 +139,183 @@ gradle.buildFinished { buildDir.deleteRecursively() } -subprojects - .filter { it.name != "module" && it.name != "platform" && it.name != "expansion" && !it.name.startsWith("impl") } - .forEach { proj -> - proj.publishing { applyToSub(proj) } +data class MavenCoordinate(val groupId: String, val artifactId: String, val version: String) + +fun Project.publishedArtifactId(): String { + val extra = extensions.extraProperties + return if (extra.has("publishId")) extra.get("publishId").toString() else name +} + +fun Project.publishedVersion(): String { + return when { + rootProject.hasProperty("devLocal") -> "${version}-local-dev" + rootProject.hasProperty("dev") -> "${version}-dev" + else -> version.toString() + } +} + +fun Project.isPublishableModule(): Boolean { + if (name == "module" || name == "platform" || name == "expansion" || name.startsWith("impl")) { + return false + } + val mainSourceSet = extensions.getByType().getByName("main") + val hasMainContent = mainSourceSet.allSource.srcDirs.any { sourceDirectory -> + sourceDirectory.isDirectory && sourceDirectory.walkTopDown().any(File::isFile) + } + return hasMainContent || path == ":common-reflex" +} + +fun Project.apiPomCoordinates(): List { + val publicDependencies = configurations.getByName("api").dependencies + + configurations.getByName("compileOnlyApi").dependencies + return publicDependencies.mapNotNull { dependency -> + when (dependency) { + is ProjectDependency -> { + val dependencyProject = dependency.dependencyProject + MavenCoordinate("io.izzel.taboolib", dependencyProject.publishedArtifactId(), dependencyProject.publishedVersion()) + } + is ExternalModuleDependency -> { + val groupId = dependency.group ?: return@mapNotNull null + val version = dependency.version ?: return@mapNotNull null + MavenCoordinate(groupId, dependency.name, version) + } + else -> null + } + }.distinct().sortedWith(compareBy(MavenCoordinate::groupId, MavenCoordinate::artifactId, MavenCoordinate::version)) +} + +fun readPomCoordinates(pomFile: File): Set { + val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pomFile) + val dependencies = document.getElementsByTagName("dependency") + return buildSet { + for (index in 0 until dependencies.length) { + val dependency = dependencies.item(index) + val children = dependency.childNodes + var groupId: String? = null + var artifactId: String? = null + var version: String? = null + for (childIndex in 0 until children.length) { + val child = children.item(childIndex) + when (child.nodeName) { + "groupId" -> groupId = child.textContent.trim() + "artifactId" -> artifactId = child.textContent.trim() + "version" -> version = child.textContent.trim() + } + } + if (groupId != null && artifactId != null && version != null) { + add(MavenCoordinate(groupId, artifactId, version)) + } + } + } +} + +fun classFileMajorVersion(classFile: File): Int { + return DataInputStream(classFile.inputStream().buffered()).use { input -> + check(input.readInt() == 0xCAFEBABE.toInt()) { "Invalid class file: $classFile" } + input.readUnsignedShort() + input.readUnsignedShort() } +} + +val verifyPublishingModel = tasks.register("verifyPublishingModel") { + group = "verification" + description = "Verifies published artifacts, excluded projects, and generated Maven dependencies." +} + +val verifyJava8Compatibility = tasks.register("verifyJava8Compatibility") { + group = "verification" + description = "Verifies Java 8 API gates and generated JVM bytecode versions." +} + +tasks.named("check") { + dependsOn(verifyPublishingModel, verifyJava8Compatibility) +} + +subprojects { + val subProject = this + afterEvaluate { + val publishable = subProject.isPublishableModule() + subProject.extensions.extraProperties.set("taboolibPublishable", publishable) + if (publishable) { + subProject.configure { applyToSub(subProject) } + } + } +} + +gradle.projectsEvaluated { + val publishableProjects = subprojects.filter { + it.extensions.extraProperties.get("taboolibPublishable") == true + } + val excludedProjects = subprojects - publishableProjects.toSet() + + verifyPublishingModel.configure { + dependsOn(publishableProjects.map { project -> + project.tasks.named("generatePomFileForMavenPublication") + }) + doLast { + publishableProjects.forEach { project -> + val publication = project.extensions.getByType() + .publications.getByName("maven") as MavenPublication + val classifiers = publication.artifacts.map { artifact -> + artifact.classifier?.takeIf(String::isNotBlank) ?: "main" + }.sorted() + check(classifiers == listOf("main", "sources")) { + "${project.path} must publish one main shadow artifact and one sources artifact, got $classifiers" + } + val pomTask = project.tasks.named("generatePomFileForMavenPublication").get() + val expectedDependencies = project.apiPomCoordinates().toSet() + val actualDependencies = readPomCoordinates(pomTask.destination) + check(actualDependencies == expectedDependencies) { + "${project.path} POM dependencies differ: expected=$expectedDependencies, actual=$actualDependencies" + } + } + excludedProjects.forEach { project -> + val publications = project.extensions.getByType().publications + check(publications.isEmpty()) { "${project.path} must not create Maven publications" } + } + } + } + + val compileTasks = subprojects.flatMap { project -> + project.tasks.withType().toList() + project.tasks.withType().toList() + } + val animalSnifferTasks = subprojects.mapNotNull { project -> + project.tasks.findByName("animalsnifferMain") + } + verifyJava8Compatibility.configure { + dependsOn(compileTasks, animalSnifferTasks) + doLast { + subprojects.forEach { project -> + project.tasks.withType().forEach { compileTask -> + check(compileTask.options.release.orNull == 8) { + "${compileTask.path} must compile with --release 8" + } + } + project.tasks.withType().forEach { compileTask -> + check(compileTask.kotlinOptions.jvmTarget == "1.8") { + "${compileTask.path} must target JVM 1.8" + } + } + } + val classFiles = subprojects.flatMap { project -> + val classesDirectory = project.layout.buildDirectory.dir("classes").get().asFile + if (classesDirectory.isDirectory) { + classesDirectory.walkTopDown().filter { it.isFile && it.extension == "class" }.toList() + } else { + emptyList() + } + } + check(classFiles.isNotEmpty()) { "No compiled classes found for Java 8 verification" } + val incompatibleClasses = classFiles.mapNotNull { classFile -> + val majorVersion = classFileMajorVersion(classFile) + if (majorVersion == 52) null else "$classFile ($majorVersion)" + } + check(incompatibleClasses.isEmpty()) { + "Non-Java-8 class files found:\n${incompatibleClasses.joinToString("\n")}" + } + } + } +} fun PublishingExtension.applyToSub(subProject: Project) { repositories { @@ -131,20 +342,26 @@ fun PublishingExtension.applyToSub(subProject: Project) { } publications { create("maven") { - // 构件名 - artifactId = if (subProject.ext.has("publishId")) subProject.ext.get("publishId").toString() else subProject.name - // 组 + artifactId = subProject.publishedArtifactId() groupId = "io.izzel.taboolib" - // 版本号 - version = when { - project.hasProperty("devLocal") -> "${project.version}-local-dev" - project.hasProperty("dev") -> "${project.version}-dev" - else -> "${project.version}" + version = subProject.publishedVersion() + artifact(subProject.tasks.named("sourcesJar")) + artifact(subProject.tasks.named("shadowJar")) + val apiDependencies = subProject.apiPomCoordinates() + if (apiDependencies.isNotEmpty()) { + pom.withXml { + val dependencies = asNode().appendNode("dependencies") + apiDependencies.forEach { dependency -> + dependencies.appendNode("dependency").apply { + appendNode("groupId", dependency.groupId) + appendNode("artifactId", dependency.artifactId) + appendNode("version", dependency.version) + appendNode("scope", "compile") + } + } + } } - // 构件 - artifact(subProject.tasks["kotlinSourcesJar"]) - artifact(subProject.tasks["shadowJar"]) println("> Apply \"$groupId:$artifactId:$version\"") } } -} \ No newline at end of file +} diff --git a/common-legacy-api/build.gradle.kts b/common-legacy-api/build.gradle.kts index d2c35ef5c..d969864cf 100644 --- a/common-legacy-api/build.gradle.kts +++ b/common-legacy-api/build.gradle.kts @@ -1,6 +1,6 @@ dependencies { - compileOnly(project(":common")) - compileOnly(project(":common-env")) - compileOnly(project(":common-platform-api")) - compileOnly(project(":common-util")) -} \ No newline at end of file + compileOnlyApi(project(":common")) + compileOnlyApi(project(":common-env")) + compileOnlyApi(project(":common-platform-api")) + compileOnlyApi(project(":common-util")) +} diff --git a/common/src/main/java/taboolib/common/ClassAppender.java b/common/src/main/java/taboolib/common/ClassAppender.java index 4945af6d5..ee1a8f60a 100644 --- a/common/src/main/java/taboolib/common/ClassAppender.java +++ b/common/src/main/java/taboolib/common/ClassAppender.java @@ -1,6 +1,5 @@ package taboolib.common; -import sun.misc.Unsafe; import taboolib.common.classloader.IsolatedClassLoader; import java.io.File; @@ -8,6 +7,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; @@ -23,18 +23,25 @@ public class ClassAppender { static MethodHandles.Lookup lookup; - static Unsafe unsafe; + static Object unsafe; + private static Method unsafeGetObject; + private static Method unsafeObjectFieldOffset; static List callbacks = new ArrayList<>(); static { try { - Field field = Unsafe.class.getDeclaredField("theUnsafe"); + Class unsafeClass = Class.forName("sun.misc.Unsafe"); + Field field = unsafeClass.getDeclaredField("theUnsafe"); field.setAccessible(true); - unsafe = (Unsafe) field.get(null); + unsafe = field.get(null); + Method unsafeStaticFieldBase = unsafeClass.getMethod("staticFieldBase", Field.class); + Method unsafeStaticFieldOffset = unsafeClass.getMethod("staticFieldOffset", Field.class); + unsafeGetObject = unsafeClass.getMethod("getObject", Object.class, long.class); + unsafeObjectFieldOffset = unsafeClass.getMethod("objectFieldOffset", Field.class); Field lookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); - Object lookupBase = unsafe.staticFieldBase(lookupField); - long lookupOffset = unsafe.staticFieldOffset(lookupField); - lookup = (MethodHandles.Lookup) unsafe.getObject(lookupBase, lookupOffset); + Object lookupBase = unsafeStaticFieldBase.invoke(unsafe, lookupField); + long lookupOffset = (long) unsafeStaticFieldOffset.invoke(unsafe, lookupField); + lookup = (MethodHandles.Lookup) unsafeGetObject.invoke(unsafe, lookupBase, lookupOffset); // 如果第二个 IMPL_LOOKUP 没有找到,提示无法加载 if (lookup == null) { PrimitiveIO.warning(t( @@ -107,7 +114,8 @@ private static void addURL(ClassLoader loader, Field ucpField, File file, boolea if (lookup == null) { throw new IllegalStateException("lookup not found"); } - Object ucp = unsafe.getObject(loader, unsafe.objectFieldOffset(ucpField)); + long ucpOffset = (long) unsafeObjectFieldOffset.invoke(unsafe, ucpField); + Object ucp = unsafeGetObject.invoke(unsafe, loader, ucpOffset); try { MethodHandle methodHandle = lookup.findVirtual(ucp.getClass(), "addURL", MethodType.methodType(void.class, URL.class)); methodHandle.invoke(ucp, file.toURI().toURL()); diff --git a/common/src/test/java/taboolib/common/ClassAppenderTest.java b/common/src/test/java/taboolib/common/ClassAppenderTest.java new file mode 100644 index 000000000..dd82b4f60 --- /dev/null +++ b/common/src/test/java/taboolib/common/ClassAppenderTest.java @@ -0,0 +1,14 @@ +package taboolib.common; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ClassAppenderTest { + + @Test + void initializesUnsafeAccessWithoutCompileTimeUnsafeDependency() { + assertNotNull(ClassAppender.unsafe); + assertNotNull(ClassAppender.lookup); + } +} diff --git a/module/database/build.gradle.kts b/module/database/build.gradle.kts index 5f7c7b7cb..0c4e66cd3 100644 --- a/module/database/build.gradle.kts +++ b/module/database/build.gradle.kts @@ -1,12 +1,13 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar dependencies { - compileOnly("com.zaxxer:HikariCP:4.0.3") - compileOnly(project(":common")) - compileOnly(project(":common-env")) - compileOnly(project(":common-platform-api")) - compileOnly(project(":common-util")) - compileOnly(project(":module:basic:basic-configuration")) + compileOnlyApi(project(":common")) + compileOnlyApi(project(":common-env")) + compileOnlyApi(project(":common-platform-api")) + compileOnlyApi(project(":common-util")) + compileOnlyApi(project(":module:basic:basic-configuration")) + compileOnlyApi("com.zaxxer:HikariCP:4.0.3") + testImplementation("com.zaxxer:HikariCP:4.0.3") testImplementation("org.xerial:sqlite-jdbc:3.42.0.0") } diff --git a/module/database/database-ptc-object/build.gradle.kts b/module/database/database-ptc-object/build.gradle.kts index 3fdd79e35..75fcfdbc2 100644 --- a/module/database/database-ptc-object/build.gradle.kts +++ b/module/database/database-ptc-object/build.gradle.kts @@ -1,10 +1,10 @@ dependencies { - compileOnly(project(":common")) - compileOnly(project(":common-util")) - compileOnly(project(":common-legacy-api")) - compileOnly(project(":common-platform-api")) - compileOnly(project(":module:database")) - compileOnly(project(":module:basic:basic-configuration")) + compileOnlyApi(project(":common")) + compileOnlyApi(project(":common-util")) + compileOnlyApi(project(":common-legacy-api")) + compileOnlyApi(project(":common-platform-api")) + compileOnlyApi(project(":module:database")) + compileOnlyApi(project(":module:basic:basic-configuration")) compileOnly("ink.ptms.core:v11701:11701-minimize:universal") testImplementation(project(":common")) testImplementation(project(":common-util")) diff --git a/module/incision/src/main/kotlin/taboolib/module/incision/weaver/SiteWeaver.kt b/module/incision/src/main/kotlin/taboolib/module/incision/weaver/SiteWeaver.kt index a9c588bb8..1031a9200 100644 --- a/module/incision/src/main/kotlin/taboolib/module/incision/weaver/SiteWeaver.kt +++ b/module/incision/src/main/kotlin/taboolib/module/incision/weaver/SiteWeaver.kt @@ -850,7 +850,7 @@ class SiteWeaver(private val sites: List) { applyPlan(replayer, ip.index, ip.plan, actions) } if (headEvents.isNotEmpty() && headInsertIdx >= 0) { - for (ev in headEvents.reversed()) { + for (ev in headEvents.asReversed()) { val emission = toEmission(ev.siteSpec, isVoid = true) replayer.insertBefore(headInsertIdx, emission) } diff --git a/module/minecraft/minecraft-kether/src/main/kotlin/taboolib/module/kether/action/transform/ActionArray.kt b/module/minecraft/minecraft-kether/src/main/kotlin/taboolib/module/kether/action/transform/ActionArray.kt index 72217737f..9b106fa6a 100644 --- a/module/minecraft/minecraft-kether/src/main/kotlin/taboolib/module/kether/action/transform/ActionArray.kt +++ b/module/minecraft/minecraft-kether/src/main/kotlin/taboolib/module/kether/action/transform/ActionArray.kt @@ -50,7 +50,7 @@ internal object ActionArray { */ @KetherParser(["reverse"]) fun actionReverse() = combinationParser { - it.group(anyAsList()).apply(it) { array -> now { array.reversed().toMutableList() } } + it.group(anyAsList()).apply(it) { array -> now { array.asReversed().toMutableList() } } } /**