diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 619b3f5fea..14896ce5a3 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -4,17 +4,29 @@ plugins { repositories { mavenCentral() + maven("https://repo.xenondevs.xyz/releases/") +} + +dependencies { + implementation("org.ow2.asm:asm:9.7") + implementation("net.fabricmc:access-widener:2.1.0") + implementation("com.google.code.gson:gson:2.11.0") + implementation("xyz.xenondevs.commons:commons-gson:1.17") } gradlePlugin { plugins { create("bundler-jar-plugin") { id = "xyz.xenondevs.bundler-jar-plugin" - implementationClass = "BundlerJarPlugin" + implementationClass = "bundler.BundlerJarPlugin" } create("bundler-plugin") { id = "xyz.xenondevs.bundler-plugin" - implementationClass = "BundlerPlugin" + implementationClass = "bundler.BundlerPlugin" + } + create("access-widener-plugin") { + id = "xyz.xenondevs.access-widener-plugin" + implementationClass = "accesswidener.AccessWidenerPlugin" } } } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/accesswidener/AccessWidenerPlugin.kt b/buildSrc/src/main/kotlin/accesswidener/AccessWidenerPlugin.kt new file mode 100644 index 0000000000..fea81cdab7 --- /dev/null +++ b/buildSrc/src/main/kotlin/accesswidener/AccessWidenerPlugin.kt @@ -0,0 +1,134 @@ +package accesswidener + +import net.fabricmc.accesswidener.AccessWidener +import net.fabricmc.accesswidener.AccessWidenerClassVisitor +import net.fabricmc.accesswidener.AccessWidenerReader +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.exclude +import org.gradle.kotlin.dsl.register +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Opcodes +import java.io.BufferedReader +import java.io.ByteArrayOutputStream +import java.io.InputStreamReader +import java.nio.file.Path +import java.security.MessageDigest +import java.util.HexFormat +import java.util.zip.ZipInputStream +import java.util.zip.ZipOutputStream +import kotlin.io.path.bufferedReader +import kotlin.io.path.exists +import kotlin.io.path.inputStream +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.notExists +import kotlin.io.path.readBytes + +private const val GROUP_ID = "xyz.xenondevs.nova" +private const val ARTIFACT_ID = "paper-server" + +class AccessWidenerPlugin : Plugin { + + override fun apply(target: Project) { + val repoDir = target.layout.projectDirectory.dir(".gradle/caches/nova-access-widener/").asFile.toPath() + + target.repositories { + maven(repoDir) { + content { includeModule(GROUP_ID, ARTIFACT_ID) } + } + } + + val accessWidenedServer = target.configurations.create("accessWidenedServer") { + defaultDependencies { + val coordinates = widenAndInstall(target, repoDir) + add(target.dependencies.create(coordinates)) + } + } + + val compileOnly = target.configurations.getByName("compileOnly") + compileOnly.extendsFrom(accessWidenedServer) + compileOnly.exclude("io.papermc.paper", "paper-server") + } + + private fun widenAndInstall(project: Project, repo: Path): String { + val devBundle = readDevBundle(project) + val (_, artifactId, version) = devBundle.mappedServerCoordinates.split(':') + + val mojangMappedServer = project.configurations.getByName("mojangMappedServer") + val serverArtifacts = mojangMappedServer.incoming.artifacts.artifactFiles + val binJar = serverArtifacts.first { it.name == "$artifactId-$version.jar" }.toPath() + val sourcesJar = binJar.parent.resolve(binJar.nameWithoutExtension + "-sources.jar") + + val accessWidener = project.layout.projectDirectory.asFile.toPath().resolve("src/main/resources/nova.accesswidener") + + val hash = hashServer(binJar, accessWidener) + + val coordinates = "$GROUP_ID:$ARTIFACT_ID:$version-$hash" + if (resolveCoordinates(repo, coordinates).notExists()) { + install(repo, coordinates, devBundle.dependencies, applyAccessWideners(accessWidener, binJar), sourcesJar) + println("access-widened server installed") + } else { + println("access-widened server was cached") + } + + return coordinates + } + + private fun readDevBundle(project: Project): DevBundleInfo { + val paperDevBundle = project.configurations.getByName("paperweightDevelopmentBundle") + .incoming.artifacts.artifactFiles.first() + ZipInputStream(paperDevBundle.inputStream()).use { zin -> + generateSequence { zin.nextEntry } + .forEach { entry -> + if (entry.name == "config.json") { + val reader = BufferedReader(InputStreamReader(zin)) + return DevBundleInfo.fromJson(reader) + } + } + } + + throw IllegalStateException("config.json not found in paperweight development bundle") + } + + private fun hashServer(jar: Path, accessWideners: Path): String { + val md = MessageDigest.getInstance("MD5") + md.update(jar.readBytes()) + md.update(accessWideners.readBytes()) + return HexFormat.of().formatHex(md.digest()) + } + + private fun applyAccessWideners(accessWidener: Path, jar: Path): ByteArray { + val widener = readAccessWidenerFile(accessWidener) + + val out = ByteArrayOutputStream() + ZipInputStream(jar.inputStream()).use { zin -> + val zout = ZipOutputStream(out) + generateSequence { zin.nextEntry } + .forEach { entry -> + zout.putNextEntry(entry) + if (entry.name.endsWith(".class")) { + val classReader = ClassReader(zin) + val classWriter = ClassWriter(0) + val widenerVisitor = AccessWidenerClassVisitor.createClassVisitor(Opcodes.ASM9, classWriter, widener) + classReader.accept(widenerVisitor, 0) + zout.write(classWriter.toByteArray()) + } else { + zin.copyTo(zout) + } + zout.closeEntry() + } + zout.close() + } + + return out.toByteArray() + } + + private fun readAccessWidenerFile(file: Path): AccessWidener { + val widener = AccessWidener() + file.bufferedReader().use { reader -> AccessWidenerReader(widener).read(reader) } + return widener + } + +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/accesswidener/DevBundleInfo.kt b/buildSrc/src/main/kotlin/accesswidener/DevBundleInfo.kt new file mode 100644 index 0000000000..7332e73c06 --- /dev/null +++ b/buildSrc/src/main/kotlin/accesswidener/DevBundleInfo.kt @@ -0,0 +1,31 @@ +package accesswidener + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import xyz.xenondevs.commons.gson.getAllStrings +import xyz.xenondevs.commons.gson.getArray +import xyz.xenondevs.commons.gson.getObject +import xyz.xenondevs.commons.gson.getString +import java.io.Reader + +data class DevBundleInfo( + val mappedServerCoordinates: String, + val dependencies: List, +) { + + companion object { + + fun fromJson(reader: Reader): DevBundleInfo { + val json = JsonParser.parseReader(reader) as JsonObject + val buildData = json.getObject("buildData") + + val dependencies = ArrayList() + dependencies.addAll(buildData.getArray("compileDependencies").getAllStrings()) + dependencies.add(json.getString("apiCoordinates")) + + return DevBundleInfo(json.getString("mappedServerCoordinates"), dependencies) + } + + } + +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/accesswidener/TinyMavenRepository.kt b/buildSrc/src/main/kotlin/accesswidener/TinyMavenRepository.kt new file mode 100644 index 0000000000..6c8e360b17 --- /dev/null +++ b/buildSrc/src/main/kotlin/accesswidener/TinyMavenRepository.kt @@ -0,0 +1,79 @@ +package accesswidener + +import java.nio.file.Path +import javax.xml.XMLConstants +import javax.xml.stream.XMLOutputFactory +import javax.xml.stream.XMLStreamWriter +import kotlin.io.path.copyTo +import kotlin.io.path.createDirectories +import kotlin.io.path.deleteIfExists +import kotlin.io.path.exists +import kotlin.io.path.outputStream +import kotlin.io.path.writeBytes +import kotlin.io.use +import kotlin.text.replace +import kotlin.text.split + +fun install( + repo: Path, + coordinates: String, + dependencyCoordinates: List, + jar: ByteArray, + sources: Path +) { + val repoJar = resolveCoordinates(repo, coordinates) + repoJar.parent.createDirectories() + repoJar.deleteIfExists() + repoJar.writeBytes(jar) + + if (sources.exists()) { + val repoSources = resolveCoordinates(repo, coordinates, classifier = "sources") + repoSources.deleteIfExists() + sources.copyTo(repoSources) + } + + val pom = resolveCoordinates(repo, coordinates, packaging = "pom") + pom.outputStream().use { out -> + val writer = XMLOutputFactory.newInstance().createXMLStreamWriter(out) + + writer.writeStartDocument("UTF-8", "1.0"); + writer.writeStartElement("project") + writer.writeNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI) + writer.writeNamespace("", "http://maven.apache.org/POM/4.0.0") + writer.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, "schemaLocation", "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd") + + writer.writeElement("modelVersion", "4.0.0") + writer.writeCoordinates(coordinates) + + writer.writeStartElement("dependencies") + for (dep in dependencyCoordinates) { + writer.writeStartElement("dependency") + writer.writeCoordinates(dep) + writer.writeEndElement() + } + writer.writeEndElement() + + writer.writeEndElement() + writer.writeEndDocument() + } +} + +fun resolveCoordinates(repo: Path, coordinates: String, classifier: String = "", packaging: String = "jar"): Path { + val (group, artifact, version) = coordinates.split(':') + val dir = repo.resolve("${group.replace('.', '/')}/$artifact/$version") + val fileName = if (classifier.isBlank()) "$artifact-$version.$packaging" else "$artifact-$version-$classifier.$packaging" + return dir.resolve(fileName) +} + +private fun XMLStreamWriter.writeElement(name: String, value: String) { + writeStartElement(name) + writeCharacters(value) + writeEndElement() +} + +private fun XMLStreamWriter.writeCoordinates(coordinates: String) { + val (group, artifact, version) = coordinates.split(':') + writeElement("groupId", group) + writeElement("artifactId", artifact) + writeElement("version", version) +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BuildBundlerJarTask.kt b/buildSrc/src/main/kotlin/bundler/BuildBundlerJarTask.kt similarity index 99% rename from buildSrc/src/main/kotlin/BuildBundlerJarTask.kt rename to buildSrc/src/main/kotlin/bundler/BuildBundlerJarTask.kt index c75fb59dc6..d5995d72d7 100644 --- a/buildSrc/src/main/kotlin/BuildBundlerJarTask.kt +++ b/buildSrc/src/main/kotlin/bundler/BuildBundlerJarTask.kt @@ -1,3 +1,4 @@ +package bundler import org.gradle.api.DefaultTask import org.gradle.api.Project diff --git a/buildSrc/src/main/kotlin/BundlerJarPlugin.kt b/buildSrc/src/main/kotlin/bundler/BundlerJarPlugin.kt similarity index 98% rename from buildSrc/src/main/kotlin/BundlerJarPlugin.kt rename to buildSrc/src/main/kotlin/bundler/BundlerJarPlugin.kt index dd106071cc..030fc2d29b 100644 --- a/buildSrc/src/main/kotlin/BundlerJarPlugin.kt +++ b/buildSrc/src/main/kotlin/bundler/BundlerJarPlugin.kt @@ -1,3 +1,4 @@ +package bundler import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/buildSrc/src/main/kotlin/BundlerPlugin.kt b/buildSrc/src/main/kotlin/bundler/BundlerPlugin.kt similarity index 96% rename from buildSrc/src/main/kotlin/BundlerPlugin.kt rename to buildSrc/src/main/kotlin/bundler/BundlerPlugin.kt index 48ba6dea4f..31a5566eff 100644 --- a/buildSrc/src/main/kotlin/BundlerPlugin.kt +++ b/buildSrc/src/main/kotlin/bundler/BundlerPlugin.kt @@ -1,3 +1,5 @@ +package bundler + import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/nova/build.gradle.kts b/nova/build.gradle.kts index d31a8389ec..4076d1b032 100644 --- a/nova/build.gradle.kts +++ b/nova/build.gradle.kts @@ -3,8 +3,9 @@ plugins { `maven-publish` alias(libs.plugins.kotlin) alias(libs.plugins.dokka) - id("xyz.xenondevs.bundler-plugin") alias(libs.plugins.paperweight) + id("xyz.xenondevs.access-widener-plugin") + id("xyz.xenondevs.bundler-plugin") } dependencies { diff --git a/nova/src/main/resources/nova.accesswidener b/nova/src/main/resources/nova.accesswidener new file mode 100644 index 0000000000..d055a528c1 --- /dev/null +++ b/nova/src/main/resources/nova.accesswidener @@ -0,0 +1 @@ +accessWidener v2 nova \ No newline at end of file