Skip to content

Commit

Permalink
Implement compile-time access-wideners
Browse files Browse the repository at this point in the history
  • Loading branch information
NichtStudioCode committed Sep 20, 2024
1 parent 2cd372a commit 036f928
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 3 deletions.
16 changes: 14 additions & 2 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
134 changes: 134 additions & 0 deletions buildSrc/src/main/kotlin/accesswidener/AccessWidenerPlugin.kt
Original file line number Diff line number Diff line change
@@ -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<Project> {

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
}

}
31 changes: 31 additions & 0 deletions buildSrc/src/main/kotlin/accesswidener/DevBundleInfo.kt
Original file line number Diff line number Diff line change
@@ -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<String>,
) {

companion object {

fun fromJson(reader: Reader): DevBundleInfo {
val json = JsonParser.parseReader(reader) as JsonObject
val buildData = json.getObject("buildData")

val dependencies = ArrayList<String>()
dependencies.addAll(buildData.getArray("compileDependencies").getAllStrings())
dependencies.add(json.getString("apiCoordinates"))

return DevBundleInfo(json.getString("mappedServerCoordinates"), dependencies)
}

}

}
79 changes: 79 additions & 0 deletions buildSrc/src/main/kotlin/accesswidener/TinyMavenRepository.kt
Original file line number Diff line number Diff line change
@@ -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<String>,
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)
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package bundler

import org.gradle.api.DefaultTask
import org.gradle.api.Project
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
package bundler

import org.gradle.api.Plugin
import org.gradle.api.Project
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package bundler

import org.gradle.api.Plugin
import org.gradle.api.Project

Expand Down
3 changes: 2 additions & 1 deletion nova/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions nova/src/main/resources/nova.accesswidener
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
accessWidener v2 nova

0 comments on commit 036f928

Please sign in to comment.