Compare commits

...

3 commits
main ... work

Author SHA1 Message Date
Kyle Wood
0ae3814cf5
broken 2021-01-17 02:46:32 -08:00
Kyle Wood
2c4b1e1409 Run paperclip patch process in separate process 2021-01-02 23:06:31 -08:00
Kyle Wood
c4eb91fe8f work 2021-01-02 21:55:01 -08:00
11 changed files with 547 additions and 307 deletions

View file

@ -30,7 +30,7 @@ tasks.withType<KotlinCompile> {
}
gradlePlugin {
// we handle publications ourselves
// we handle publications manually
isAutomatedPublishing = false
}
@ -79,6 +79,10 @@ dependencies {
shade("net.fabricmc:lorenz-tiny:3.0.0")
shade("io.sigpipe:jbsdiff:1.0")
val jgraphtVersion = "1.5.0"
shade("org.jgrapht:jgrapht-core:$jgraphtVersion")
shade("org.jgrapht:jgrapht-io:$jgraphtVersion")
}
ktlint {
@ -124,13 +128,17 @@ tasks.shadowJar {
"io.sigpipe",
"me.jamiemansfield",
"net.fabricmc",
"org.antlr",
"org.apache.commons.codec",
"org.apache.commons.compress",
"org.apache.commons.logging",
"org.apache.commons.text",
"org.apache.felix",
"org.apache.http",
"org.cadixdev",
"org.eclipse",
"org.jgrapht",
"org.jheaps",
"org.objectweb",
"org.osgi",
"org.tukaani"

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -92,10 +92,12 @@ class Paperweight : Plugin<Project> {
val tasks = target.createTasks()
val generatePaperclipPatch by target.tasks.registering<GeneratePaperclipPatch>()
// Setup the server jar
target.afterEvaluate {
target.ext.serverProject.forUseAtConfigurationTime().orNull?.setupServerProject(target, tasks.spigotTasks)?.let { reobfJar ->
val generatePaperclipPatch by target.tasks.registering<GeneratePaperclipPatch> {
generatePaperclipPatch.configure {
originalJar.set(tasks.generalTasks.downloadServerJar.flatMap { it.outputJar })
patchedJar.set(reobfJar.flatMap { it.outputJar })
mcVersion.set(target.ext.minecraftVersion)
@ -103,13 +105,13 @@ class Paperweight : Plugin<Project> {
target.tasks.named("jar", Jar::class) {
val paperclipConfig = target.configurations.named(Constants.PAPERCLIP_CONFIG)
dependsOn(paperclipConfig)
dependsOn(paperclipConfig, generatePaperclipPatch)
val paperclipZip = target.zipTree(paperclipConfig.map { it.singleFile })
from(paperclipZip) {
exclude("META-INF/MANIFEST.MF")
}
from(target.zipTree(generatePaperclipPatch.flatMap { it.outputZip }.get()))
from(target.zipTree(generatePaperclipPatch.flatMap { it.outputZip }))
manifest.from(paperclipZip.matching { include("META-INF/MANIFEST.MF") }.singleFile)
}
@ -150,7 +152,7 @@ class Paperweight : Plugin<Project> {
val initialTasks = createInitialTasks()
val generalTasks = createGeneralTasks()
val vanillaTasks = createVanillaTasks(initialTasks, generalTasks)
val spigotTasks = createSpigotTasks(initialTasks, generalTasks, vanillaTasks)
val spigotTasks = createSpigotTasks(generalTasks, vanillaTasks)
val applyMergedAt by tasks.registering<ApplyAccessTransform> {
inputJar.set(vanillaTasks.fixJar.flatMap { it.outputJar })
@ -236,6 +238,7 @@ class Paperweight : Plugin<Project> {
)
data class VanillaTasks(
val inspectVanillaJar: TaskProvider<InspectVanillaJar>,
val generateMappings: TaskProvider<GenerateMappings>,
val fixJar: TaskProvider<FixJar>,
val downloadMcLibraries: TaskProvider<DownloadMcLibraries>
@ -334,11 +337,29 @@ class Paperweight : Plugin<Project> {
val cache: File = layout.cache
val downloadService = download
val downloadMcLibraries by tasks.registering<DownloadMcLibraries> {
mcLibrariesFile.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
mcRepo.set(Constants.MC_LIBRARY_URL)
outputDir.set(cache.resolve(Constants.MINECRAFT_JARS_PATH))
sourcesOutputDir.set(cache.resolve(Constants.MINECRAFT_SOURCES_PATH))
downloader.set(downloadService)
}
val inspectVanillaJar by tasks.registering<InspectVanillaJar> {
inputJar.set(generalTasks.downloadServerJar.flatMap { it.outputJar })
librariesDir.set(downloadMcLibraries.flatMap { it.outputDir })
mcLibraries.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
serverLibraries.set(cache.resolve(Constants.SERVER_LIBRARIES))
}
val generateMappings by tasks.registering<GenerateMappings> {
vanillaJar.set(generalTasks.filterVanillaJar.flatMap { it.outputJar })
vanillaMappings.set(initialTasks.downloadMappings.flatMap { it.outputFile })
paramMappings.fileProvider(configurations.named(Constants.PARAM_MAPPINGS_CONFIG).map { it.singleFile })
methodOverrides.set(inspectVanillaJar.flatMap { it.methodOverrides })
outputMappings.set(cache.resolve(Constants.MOJANG_YARN_MAPPINGS))
}
@ -356,25 +377,16 @@ class Paperweight : Plugin<Project> {
vanillaJar.set(generalTasks.downloadServerJar.flatMap { it.outputJar })
}
val downloadMcLibraries by tasks.registering<DownloadMcLibraries> {
mcLibrariesFile.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
mcRepo.set(Constants.MC_LIBRARY_URL)
outputDir.set(cache.resolve(Constants.MINECRAFT_JARS_PATH))
sourcesOutputDir.set(cache.resolve(Constants.MINECRAFT_SOURCES_PATH))
downloader.set(downloadService)
}
return VanillaTasks(generateMappings, fixJar, downloadMcLibraries)
return VanillaTasks(inspectVanillaJar, generateMappings, fixJar, downloadMcLibraries)
}
private fun Project.createSpigotTasks(initialTasks: InitialTasks, generalTasks: GeneralTasks, vanillaTasks: VanillaTasks): SpigotTasks {
private fun Project.createSpigotTasks(generalTasks: GeneralTasks, vanillaTasks: VanillaTasks): SpigotTasks {
val cache: File = layout.cache
val extension: PaperweightExtension = ext
val downloadService = download
val (buildDataInfo, downloadServerJar, filterVanillaJar) = generalTasks
val (generateMappings, _, _) = vanillaTasks
val (inspectVanillaJar, generateMappings, _, _) = vanillaTasks
val addAdditionalSpigotMappings by tasks.registering<AddAdditionalSpigotMappings> {
classSrg.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.classMappings }))
@ -383,14 +395,6 @@ class Paperweight : Plugin<Project> {
additionalMemberEntriesSrg.set(extension.paper.additionalSpigotMemberMappings)
}
val inspectVanillaJar by tasks.registering<InspectVanillaJar> {
inputJar.set(downloadServerJar.flatMap { it.outputJar })
librariesDir.set(vanillaTasks.downloadMcLibraries.flatMap { it.outputDir })
mcLibraries.set(initialTasks.setupMcLibraries.flatMap { it.outputFile })
serverLibraries.set(cache.resolve(Constants.SERVER_LIBRARIES))
}
val generateSpigotMappings by tasks.registering<GenerateSpigotMappings> {
classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg })
memberMappings.set(addAdditionalSpigotMappings.flatMap { it.outputMemberSrg })
@ -399,6 +403,7 @@ class Paperweight : Plugin<Project> {
loggerFields.set(inspectVanillaJar.flatMap { it.loggerFile })
paramIndexes.set(inspectVanillaJar.flatMap { it.paramIndexes })
syntheticMethods.set(inspectVanillaJar.flatMap { it.syntheticMethods })
methodOverrides.set(inspectVanillaJar.flatMap { it.methodOverrides })
sourceMappings.set(generateMappings.flatMap { it.outputMappings })

View file

@ -23,6 +23,7 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.AsmUtil
import io.papermc.paperweight.util.ClassNodeCache
import io.papermc.paperweight.util.SyntheticUtil
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
@ -33,7 +34,6 @@ 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.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
@ -75,8 +75,7 @@ abstract class FixJar : BaseTask(), AsmUtil {
}
try {
val node =
classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
val node = classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
ParameterAnnotationFixer(node).visitNode()
OverrideAnnotationAdder(node, classNodeCache).visitNode()
@ -230,49 +229,3 @@ class OverrideAnnotationAdder(private val node: ClassNode, private val classNode
return result
}
}
class ClassNodeCache(private val jarFile: JarFile, private val fallbackJar: JarFile) {
private val classNodeMap = hashMapOf<String, ClassNode?>()
fun findClass(name: String): ClassNode? {
return classNodeMap.computeIfAbsent(normalize(name)) { fileName ->
val classData = findClassData(fileName) ?: return@computeIfAbsent null
val classReader = ClassReader(classData)
val node = ClassNode(Opcodes.ASM9)
classReader.accept(node, 0)
return@computeIfAbsent node
}
}
private fun findClassData(className: String): ByteArray? {
val entry = ZipEntry(className)
return (
jarFile.getInputStream(entry) // remapped class
?: fallbackJar.getInputStream(entry) // library class
?: ClassLoader.getSystemResourceAsStream(className)
)?.use { it.readBytes() } // JDK class
}
private fun normalize(name: String): String {
var workingName = name
if (workingName.endsWith(".class")) {
workingName = workingName.substring(0, workingName.length - 6)
}
var startIndex = 0
var endIndex = workingName.length
if (workingName.startsWith('L')) {
startIndex = 1
}
if (workingName.endsWith(';')) {
endIndex--
}
return workingName.substring(startIndex, endIndex).replace('.', '/') + ".class"
}
fun clear() {
classNodeMap.clear()
}
}

View file

@ -30,6 +30,7 @@ import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.path
import java.nio.file.FileSystems
import java.nio.file.Files
import java.util.zip.ZipFile
import javax.inject.Inject
import org.cadixdev.atlas.Atlas
import org.cadixdev.bombe.asm.jar.JarEntryRemappingTransformer
@ -41,6 +42,7 @@ import org.cadixdev.lorenz.merge.MappingSetMergerHandler
import org.cadixdev.lorenz.merge.MergeConfig
import org.cadixdev.lorenz.merge.MergeContext
import org.cadixdev.lorenz.merge.MergeResult
import org.cadixdev.lorenz.merge.MethodMergeStrategy
import org.cadixdev.lorenz.model.ClassMapping
import org.cadixdev.lorenz.model.FieldMapping
import org.cadixdev.lorenz.model.InnerClassMapping
@ -66,6 +68,8 @@ abstract class GenerateMappings : DefaultTask() {
abstract val vanillaMappings: RegularFileProperty
@get:InputFile
abstract val paramMappings: RegularFileProperty
@get:InputFile
abstract val methodOverrides: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@ -86,16 +90,17 @@ abstract class GenerateMappings : DefaultTask() {
vanillaMappings,
paramMappings,
MergeConfig.builder()
.withMethodMergeStrategy(MethodMergeStrategy.STRICT)
.withFieldMergeStrategy(FieldMergeStrategy.STRICT)
.withMergeHandler(ParamsMergeHandler())
.build()
).merge()
ensureParentExists(outputMappings)
// Fill out any missing inheritance info in the mappings
val tempMappingsFile = Files.createTempFile("mappings", "tiny")
val tempMappingsOutputFile = Files.createTempFile("mappings-out", "tiny")
val filledMerged = try {
try {
MappingFormats.TINY.write(merged, tempMappingsFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
val queue = workerExecutor.processIsolation {
@ -105,21 +110,65 @@ abstract class GenerateMappings : DefaultTask() {
queue.submit(AtlasAction::class) {
inputJar.set(vanillaJar.file)
mappingsFile.set(tempMappingsFile.toFile())
outputMappingsFile.set(tempMappingsOutputFile.toFile())
outputMappingsFile.set(outputMappings.file)
}
queue.await()
MappingFormats.TINY.read(tempMappingsOutputFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
} finally {
Files.deleteIfExists(tempMappingsFile)
Files.deleteIfExists(tempMappingsOutputFile)
}
}
/*
private fun copyOverridenParamMappings(mappings: MappingSet) {
val methods = hashMapOf<String, String>()
methodOverrides.file.reader(Charsets.UTF_8).useLines { lines ->
lines.map { line ->
val (method, superMethod) = line.split("|")
methods[method] = superMethod
}
}
ensureParentExists(outputMappings)
MappingFormats.TINY.write(filledMerged, outputMappings.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
for (classMapping in mappings.topLevelClassMappings) {
copyOverridenParamMappings(mappings, classMapping, methods)
}
}
private fun copyOverridenParamMappings(mappingSet: MappingSet, classMapping: ClassMapping<*, *>, methods: Map<String, String>) {
methodLoop@for (mapping in classMapping.methodMappings) {
if (mapping.parameterMappings.isNotEmpty()) {
continue
}
var baseClassName = classMapping.obfuscatedName
var baseMethod = mapping.obfuscatedName
var baseDesc = mapping.obfuscatedDescriptor
while (true) {
val superMethod = methods["$baseClassName,$baseMethod,$baseDesc"] ?: continue@methodLoop
val (className, methodName, methodDesc) = superMethod.split(",")
val superMethodMapping = mappingSet.getClassMapping(className).orNull?.getMethodMapping(methodName, methodDesc)?.orNull
if (superMethodMapping != null) {
superMethodMapping.parameterMappings.forEach { it.copy(mapping) }
break
}
// prevent possible infinite loops
if (baseClassName == className) {
break
}
baseClassName = className
baseMethod = methodName
baseDesc = methodDesc
}
}
classMapping.innerClassMappings.forEach { copyOverridenParamMappings(mappingSet, it, methods) }
}
*/
abstract class AtlasAction : WorkAction<AtlasParameters> {
override fun execute() {
val mappings = MappingFormats.TINY.read(parameters.mappingsFile.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
@ -237,7 +286,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<MethodMapping?> {
return MergeResult(
target.createMethodMapping(left.signature, left.deobfuscatedName),
listOfNotNull(standardRightDuplicate, wiggledRightDuplicate)
listOfNotNull(standardRightDuplicate)
)
}

View file

@ -36,11 +36,16 @@ import java.nio.file.StandardCopyOption
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Properties
import javax.inject.Inject
import kotlin.experimental.and
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.kotlin.dsl.submit
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
abstract class GeneratePaperclipPatch : ZippedTask() {
@ -51,88 +56,122 @@ abstract class GeneratePaperclipPatch : ZippedTask() {
@get:Input
abstract val mcVersion: Property<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun run(rootDir: File) {
val patchFile = rootDir.resolve("paperMC.patch").toPath()
val propFile = rootDir.resolve("patch.properties").toPath()
val protocol = rootDir.resolve("META-INF/$PROTOCOL_FILE").toPath()
val zipUri = try {
val jarUri = patchedJar.file.toURI()
URI("jar:${jarUri.scheme}", jarUri.path, null)
} catch (e: URISyntaxException) {
throw PaperweightException("Failed to create jar URI for $patchedJar", e)
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs("-Xmx2G")
}
try {
FileSystems.newFileSystem(zipUri, mapOf<String, Any>()).use { zipFs ->
val protocolPath = zipFs.getPath("META-INF", PROTOCOL_FILE)
if (Files.notExists(protocolPath)) {
Files.deleteIfExists(protocol)
return@use
queue.submit(PaperclipAction::class) {
zipRootDir.set(rootDir)
originalJar.set(this@GeneratePaperclipPatch.originalJar.file)
patchedJar.set(this@GeneratePaperclipPatch.patchedJar.file)
mcVersion.set(this@GeneratePaperclipPatch.mcVersion.get())
}
queue.await()
}
abstract class PaperclipAction : WorkAction<PaperclipParameters> {
override fun execute() {
val rootDir = parameters.zipRootDir.path
val originalJarPath = parameters.originalJar.path
val patchedJarPath = parameters.patchedJar.path
val patchFile = rootDir.resolve("paperMC.patch")
val propFile = rootDir.resolve("patch.properties")
val protocol = rootDir.resolve("META-INF/$PROTOCOL_FILE")
val zipUri = try {
val jarUri = patchedJarPath.toUri()
URI("jar:${jarUri.scheme}", jarUri.path, null)
} catch (e: URISyntaxException) {
throw PaperweightException("Failed to create jar URI for $patchedJarPath", e)
}
try {
FileSystems.newFileSystem(zipUri, mapOf<String, Any>()).use { zipFs ->
val protocolPath = zipFs.getPath("META-INF", PROTOCOL_FILE)
if (Files.notExists(protocolPath)) {
Files.deleteIfExists(protocol)
return@use
}
Files.createDirectories(protocol.parent)
Files.copy(protocolPath, protocol, StandardCopyOption.REPLACE_EXISTING)
}
Files.createDirectories(protocol.parent)
Files.copy(protocolPath, protocol, StandardCopyOption.REPLACE_EXISTING)
} catch (e: IOException) {
throw PaperweightException("Failed to read $patchedJarPath contents", e)
}
} catch (e: IOException) {
throw PaperweightException("Failed to read $patchedJar contents", e)
}
// Read the files into memory
println("Reading jars into memory")
val originalBytes = Files.readAllBytes(originalJar.path)
val patchedBytes = Files.readAllBytes(patchedJar.path)
// Read the files into memory
println("Reading jars into memory")
val originalBytes = Files.readAllBytes(originalJarPath)
val patchedBytes = Files.readAllBytes(patchedJarPath)
println("Creating Paperclip patch")
try {
Files.newOutputStream(patchFile).use { patchOutput ->
Diff.diff(originalBytes, patchedBytes, patchOutput)
println("Creating Paperclip patch")
try {
Files.newOutputStream(patchFile).use { patchOutput ->
Diff.diff(originalBytes, patchedBytes, patchOutput)
}
} catch (e: Exception) {
throw PaperweightException("Error creating patch between $originalJarPath and $patchedJarPath", e)
}
// Add the SHA-256 hashes for the files
val digestSha256 = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA-256 hasher", e)
}
// Vanilla's URL uses a SHA1 hash of the vanilla server jar
val digestSha1 = try {
MessageDigest.getInstance("SHA1")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA1 hasher", e)
}
println("Hashing files")
val originalSha1 = digestSha1.digest(originalBytes)
val originalSha256 = digestSha256.digest(originalBytes)
val patchedSha256 = digestSha256.digest(patchedBytes)
val prop = Properties()
prop["originalHash"] = toHex(originalSha256)
prop["patchedHash"] = toHex(patchedSha256)
prop["patch"] = "paperMC.patch"
prop["sourceUrl"] = "https://launcher.mojang.com/v1/objects/" + toHex(originalSha1).toLowerCase() + "/server.jar"
prop["version"] = parameters.mcVersion.get()
println("Writing properties file")
Files.newBufferedWriter(propFile).use { writer ->
prop.store(
writer,
"Default Paperclip launch values. Can be overridden by placing a paperclip.properties file in the server directory."
)
}
} catch (e: Exception) {
throw PaperweightException("Error creating patch between ${originalJar.path} and ${patchedJar.path}", e)
}
// Add the SHA-256 hashes for the files
val digestSha256 = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA-256 hasher", e)
private fun toHex(hash: ByteArray): String {
val sb: StringBuilder = StringBuilder(hash.size * 2)
for (aHash in hash) {
sb.append("%02X".format(aHash and 0xFF.toByte()))
}
return sb.toString()
}
// Vanilla's URL uses a SHA1 hash of the vanilla server jar
val digestSha1 = try {
MessageDigest.getInstance("SHA1")
} catch (e: NoSuchAlgorithmException) {
throw PaperweightException("Could not create SHA1 hasher", e)
}
println("Hashing files")
val originalSha1 = digestSha1.digest(originalBytes)
val originalSha256 = digestSha256.digest(originalBytes)
val patchedSha256 = digestSha256.digest(patchedBytes)
val prop = Properties()
prop["originalHash"] = toHex(originalSha256)
prop["patchedHash"] = toHex(patchedSha256)
prop["patch"] = "paperMC.patch"
prop["sourceUrl"] = "https://launcher.mojang.com/v1/objects/" + toHex(originalSha1).toLowerCase() + "/server.jar"
prop["version"] = mcVersion.get()
println("Writing properties file")
Files.newBufferedWriter(propFile).use { writer ->
prop.store(writer, "Default Paperclip launch values. Can be overridden by placing a paperclip.properties file in the server directory.")
companion object {
private const val PROTOCOL_FILE = "io.papermc.paper.daemon.protocol"
}
}
private fun toHex(hash: ByteArray): String {
val sb: StringBuilder = StringBuilder(hash.size * 2)
for (aHash in hash) {
sb.append("%02X".format(aHash and 0xFF.toByte()))
}
return sb.toString()
}
companion object {
const val PROTOCOL_FILE = "io.papermc.paper.daemon.protocol"
interface PaperclipParameters : WorkParameters {
val zipRootDir: RegularFileProperty
val originalJar: RegularFileProperty
val patchedJar: RegularFileProperty
val mcVersion: Property<String>
}
}

View file

@ -22,14 +22,16 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Constants
import io.papermc.paperweight.util.Constants.DEOBF_NAMESPACE
import io.papermc.paperweight.util.Constants.OBF_NAMESPACE
import io.papermc.paperweight.util.Constants.SPIGOT_NAMESPACE
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.MethodRef
import io.papermc.paperweight.util.emptyMergeResult
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.orNull
import io.papermc.paperweight.util.parentClass
import io.papermc.paperweight.util.readOverrides
import io.papermc.paperweight.util.path
import org.cadixdev.bombe.type.signature.MethodSignature
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.merge.MappingSetMerger
import org.cadixdev.lorenz.merge.MappingSetMergerHandler
@ -46,6 +48,8 @@ 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.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.traverse.DepthFirstIterator
abstract class GenerateSpigotMappings : DefaultTask() {
@ -62,6 +66,8 @@ abstract class GenerateSpigotMappings : DefaultTask() {
abstract val paramIndexes: RegularFileProperty
@get:InputFile
abstract val syntheticMethods: RegularFileProperty
@get:InputFile
abstract val methodOverrides: RegularFileProperty
@get:InputFile
abstract val sourceMappings: RegularFileProperty
@ -84,13 +90,10 @@ abstract class GenerateSpigotMappings : DefaultTask() {
// Get the new package name
val newPackage = packageMappings.asFile.get().readLines()[0].split(Regex("\\s+"))[1]
val sourceMappings = MappingFormats.TINY.read(
sourceMappings.path,
Constants.OBF_NAMESPACE,
Constants.DEOBF_NAMESPACE
)
val sourceMappings = MappingFormats.TINY.read(sourceMappings.path, OBF_NAMESPACE, DEOBF_NAMESPACE)
val synths = hashMapOf<String, MutableMap<String, MutableMap<String, String>>>()
/*
val synths = newSynths()
syntheticMethods.file.useLines { lines ->
for (line in lines) {
val (className, desc, synthName, baseName) = line.split(" ")
@ -98,12 +101,15 @@ abstract class GenerateSpigotMappings : DefaultTask() {
.computeIfAbsent(desc) { hashMapOf() }[baseName] = synthName
}
}
*/
val overrides = readOverrides(methodOverrides)
val notchToSpigotSet = MappingSetMerger.create(
mergedMappingSet,
sourceMappings,
MergeConfig.builder()
.withMergeHandler(SpigotMappingsMergerHandler(newPackage, synths))
.withMergeHandler(SpigotMappingsMergerHandler(newPackage, overrides))
.build()
).merge()
@ -111,12 +117,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
val cleanedSourceMappings = removeLambdaMappings(adjustedSourceMappings)
val spigotToNamedSet = notchToSpigotSet.reverse().merge(cleanedSourceMappings)
MappingFormats.TINY.write(
spigotToNamedSet,
outputMappings.path,
Constants.SPIGOT_NAMESPACE,
Constants.DEOBF_NAMESPACE
)
MappingFormats.TINY.write(spigotToNamedSet, outputMappings.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
}
private fun adjustParamIndexes(mappings: MappingSet): MappingSet {
@ -151,7 +152,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
params: Map<String, Map<String, Map<Int, Int>>>
) {
for (mapping in from.fieldMappings) {
to.createFieldMapping(mapping.signature, mapping.deobfuscatedName)
mapping.copy(to)
}
for (mapping in from.innerClassMappings) {
val newMapping = to.createInnerClassMapping(mapping.obfuscatedName, mapping.deobfuscatedName)
@ -169,7 +170,7 @@ abstract class GenerateSpigotMappings : DefaultTask() {
val methodMap = classMap[mapping.signature.toJvmsIdentifier()] ?: continue
for (paramMapping in paramMappings) {
val i = methodMap[paramMapping.index] ?: paramMapping.index
val i = methodMap[paramMapping.index] ?: continue
newMapping.createParameterMapping(i, paramMapping.deobfuscatedName)
}
}
@ -210,11 +211,14 @@ abstract class GenerateSpigotMappings : DefaultTask() {
}
}
/*
typealias Synths = Map<String, Map<String, Map<String, String>>>
fun newSynths() = hashMapOf<String, MutableMap<String, MutableMap<String, String>>>()
*/
class SpigotMappingsMergerHandler(
private val newPackage: String,
private val synths: Synths
private val methodOverrides: SimpleDirectedGraph<MethodRef, *>
) : MappingSetMergerHandler {
override fun mergeTopLevelClassMappings(
@ -298,7 +302,7 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<InnerClassMapping?> {
// We want to get all of the inner classes from SRG, but not the SRG names
// We want to get all of the members from mojmap, but not the mojmap names
return MergeResult(target.createInnerClassMapping(right.obfuscatedName, right.obfuscatedName), right)
}
@ -320,6 +324,8 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>,
context: MergeContext
): FieldMapping {
// Spigot mappings don't include signature, so see if we can pull the signature out of the mojmap
// Regardless, still keep Spigot's name
val right = strictRightDuplicate ?: looseRightDuplicate ?: strictRightContinuation ?: looseRightContinuation ?: left
return target.createFieldMapping(right.signature, left.deobfuscatedName)
}
@ -342,33 +348,81 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val ref = MethodRef.from(left.parent.obfuscatedName, left.signature)
val newMapping = if (methodOverrides.containsVertex(ref)) {
val methodRef = DepthFirstIterator(methodOverrides, ref).asSequence().firstOrNull() ?: ref
target.getOrCreateMethodMapping(methodRef.methodName, methodRef.methodDesc).also {
it.deobfuscatedName = left.deobfuscatedName
}
} else {
target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
}
return MergeResult(newMapping)
/*
val newMapping = target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
// Check if Spigot calls this mapping something else
val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor)
val newName = synthMethods?.get(left.obfuscatedName)
return if (newName != null) {
// Spigot does call this mapping something else, we need to find it first
val newLeftMapping = left.parentClass.getMethodMapping(MethodSignature(newName, left.descriptor)).orNull
val newMapping = if (newLeftMapping != null) {
// We found it, use it for the left side instead
target.getOrCreateMethodMapping(newLeftMapping.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
} else {
// Can't find the name spigot uses, check the hierarchy
val methodRef = MethodRef(newMapping.parent.obfuscatedName, newMapping.obfuscatedName, newMapping.descriptor.toString())
target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = newName
}
}
val methodRef = MethodRef(newMapping.parent.obfuscatedName, newMapping.obfuscatedName, newMapping.descriptor.toString())
if (methodOverrides.containsVertex(methodRef)) {
for ((superClass, superName, superDesc) in BreadthFirstIterator(methodOverrides, methodRef)) {
}
}
MergeResult(newMapping)
} else {
// normal mapping
val newMapping = target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
return MergeResult(newMapping)
}
*/
}
override fun addLeftMethodMapping(
left: MethodMapping,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val ref = MethodRef.from(left.parent.obfuscatedName, left.signature)
val newMapping = if (methodOverrides.containsVertex(ref)) {
val methodRef = DepthFirstIterator(methodOverrides, ref).asSequence().firstOrNull() ?: ref
target.getOrCreateMethodMapping(methodRef.methodName, methodRef.methodDesc).also {
it.deobfuscatedName = left.deobfuscatedName
}
} else {
target.getOrCreateMethodMapping(left.signature).also {
it.deobfuscatedName = left.deobfuscatedName
}
}
return MergeResult(newMapping)
/*
// Check if Spigot maps this from a synthetic method name
var obfName: String? = null
val synthMethods = synths[left.parent.fullObfuscatedName]?.get(left.obfuscatedDescriptor)
@ -383,12 +437,14 @@ class SpigotMappingsMergerHandler(
}
if (obfName == null) {
// This mapping doesn't actually exist, drop it
return emptyMergeResult()
}
val newMapping = target.getOrCreateMethodMapping(obfName, left.descriptor)
newMapping.deobfuscatedName = left.deobfuscatedName
return MergeResult(newMapping)
*/
}
override fun addLeftFieldMapping(left: FieldMapping, target: ClassMapping<*, *>, context: MergeContext): FieldMapping? {
@ -410,6 +466,20 @@ class SpigotMappingsMergerHandler(
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val ref = MethodRef.from(right.parent.obfuscatedName, right.signature)
if (methodOverrides.containsVertex(ref)) {
val methodRef = DepthFirstIterator(methodOverrides, ref).asSequence().firstOrNull() ?: ref
val originalMapping = context.left.getClassMapping(methodRef.className).orNull
?.getMethodMapping(methodRef.methodName, methodRef.methodDesc)?.orNull ?: return emptyMergeResult()
val newMapping = target.getOrCreateMethodMapping(right.signature).also {
it.deobfuscatedName = originalMapping.deobfuscatedName
}
return MergeResult(newMapping)
} else {
return emptyMergeResult()
}
/*
// Check if spigot changes this method automatically
val synthMethods = synths[right.parentClass.fullObfuscatedName]?.get(right.obfuscatedDescriptor)
val newName = synthMethods?.get(right.obfuscatedName) ?: return emptyMergeResult()
@ -423,6 +493,7 @@ class SpigotMappingsMergerHandler(
newMapping.deobfuscatedName = newName
}
return MergeResult(newMapping)
*/
}
private fun prependPackage(name: String): String {

View file

@ -23,11 +23,16 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.AsmUtil
import io.papermc.paperweight.util.ClassNodeCache
import io.papermc.paperweight.util.MavenArtifact
import io.papermc.paperweight.util.MethodRef
import io.papermc.paperweight.util.SyntheticUtil
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.isLibraryJar
import io.papermc.paperweight.util.writeOverrides
import java.util.concurrent.atomic.AtomicInteger
import java.util.jar.JarFile
import java.util.zip.ZipFile
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
@ -35,13 +40,15 @@ import org.gradle.api.tasks.InputDirectory
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.ClassVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.jgrapht.graph.DefaultEdge
import org.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.nio.Attribute
import org.jgrapht.nio.DefaultAttribute
import org.jgrapht.nio.dot.DOTExporter
import org.jgrapht.nio.dot.DOTImporter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.ClassNode
abstract class InspectVanillaJar : BaseTask() {
@ -59,12 +66,15 @@ abstract class InspectVanillaJar : BaseTask() {
@get:OutputFile
abstract val syntheticMethods: RegularFileProperty
@get:OutputFile
abstract val methodOverrides: RegularFileProperty
@get:OutputFile
abstract val serverLibraries: RegularFileProperty
override fun init() {
loggerFile.convention(defaultOutput("$name-loggerFields", "txt"))
paramIndexes.convention(defaultOutput("$name-paramIndexes", "txt"))
syntheticMethods.convention(defaultOutput("$name-syntheticMethods", "txt"))
methodOverrides.convention(defaultOutput("$name-methodOverrides", "json"))
}
@TaskAction
@ -72,28 +82,32 @@ abstract class InspectVanillaJar : BaseTask() {
val loggers = mutableListOf<LoggerFields.Data>()
val params = mutableListOf<ParamIndexes.Data>()
val synthMethods = mutableListOf<SyntheticMethods.Data>()
val overrides = SimpleDirectedGraph<MethodRef, DefaultEdge>(DefaultEdge::class.java)
var visitor: ClassVisitor
visitor = LoggerFields.Visitor(null, loggers)
visitor = ParamIndexes.Visitor(visitor, params)
visitor = SyntheticMethods.Visitor(visitor, synthMethods)
JarFile(inputJar.file).use { jarFile ->
val classNodeCache = ClassNodeCache(jarFile)
archives.zipTree(inputJar.file).matching {
include("/*.class")
include("/net/minecraft/**/*.class")
}.forEach { file ->
if (file.isDirectory) {
return@forEach
for (entry in jarFile.entries()) {
if (!entry.name.endsWith(".class")) {
continue
}
if (entry.name.count { it == '/' } > 0 && !entry.name.startsWith("net/minecraft/")) {
continue
}
val node = classNodeCache.findClass(entry.name) ?: error("No ClassNode found for known entry")
LoggerFields.Visitor.visit(node, loggers)
ParamIndexes.Visitor.visit(node, params)
SyntheticMethods.Visitor.visit(node, synthMethods)
MethodOverrides.Visitor.visit(node, classNodeCache, overrides)
}
val classData = file.readBytes()
ClassReader(classData).accept(visitor, 0)
}
val serverLibs = checkLibraries()
loggerFile.file.bufferedWriter(Charsets.UTF_8).use { writer ->
loggers.sort()
loggers.sort()
loggerFile.file.bufferedWriter().use { writer ->
for (loggerField in loggers) {
writer.append(loggerField.className)
writer.append(' ')
@ -102,8 +116,8 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
paramIndexes.file.bufferedWriter(Charsets.UTF_8).use { writer ->
params.sort()
params.sort()
paramIndexes.file.bufferedWriter().use { writer ->
for (methodData in params) {
writer.append(methodData.className)
writer.append(' ')
@ -120,8 +134,8 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
syntheticMethods.file.bufferedWriter(Charsets.UTF_8).use { writer ->
synthMethods.sort()
synthMethods.sort()
syntheticMethods.file.bufferedWriter().use { writer ->
for ((className, desc, synthName, baseName) in synthMethods) {
writer.append(className)
writer.append(' ')
@ -134,8 +148,11 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
serverLibraries.file.bufferedWriter(Charsets.UTF_8).use { writer ->
serverLibs.map { it.toString() }.sorted().forEach { artifact ->
writeOverrides(overrides, methodOverrides)
val libs = serverLibs.map { it.toString() }.sorted()
serverLibraries.file.bufferedWriter().use { writer ->
libs.forEach { artifact ->
writer.appendln(artifact)
}
}
@ -173,50 +190,22 @@ abstract class InspectVanillaJar : BaseTask() {
}
}
abstract class BaseClassVisitor(classVisitor: ClassVisitor?) : ClassVisitor(Opcodes.ASM8, classVisitor), AsmUtil {
protected var currentClass: String? = null
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
this.currentClass = name
super.visit(version, access, name, signature, superName, interfaces)
}
}
/*
* SpecialSource2 automatically maps all Logger fields to the name LOGGER, without needing mappings defined, so we need
* to make a note of all of those fields
*/
object LoggerFields {
class Visitor(
classVisitor: ClassVisitor?,
private val fields: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
object Visitor : AsmUtil {
override fun visitField(
access: Int,
name: String,
descriptor: String,
signature: String?,
value: Any?
): FieldVisitor? {
val ret = super.visitField(access, name, descriptor, signature, value)
val className = currentClass ?: return ret
if (Opcodes.ACC_STATIC !in access || Opcodes.ACC_FINAL !in access) {
return ret
fun visit(node: ClassNode, fields: MutableList<Data>) {
for (field in node.fields) {
if (Opcodes.ACC_STATIC !in field.access || Opcodes.ACC_FINAL !in field.access) {
continue
}
if (field.desc == "Lorg/apache/logging/log4j/Logger;") {
fields += Data(node.name, field.name)
}
}
if (descriptor != "Lorg/apache/logging/log4j/Logger;") {
return ret
}
fields += Data(className, name)
return ret
}
}
@ -235,45 +224,33 @@ object LoggerFields {
* actual bytecode to translate the LVT-based indexes back to 0-based param indexes.
*/
object ParamIndexes {
class Visitor(
classVisitor: ClassVisitor?,
private val methods: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
object Visitor : AsmUtil {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val ret = super.visitMethod(access, name, descriptor, signature, exceptions)
val className = currentClass ?: return ret
fun visit(node: ClassNode, methods: MutableList<Data>) {
for (method in node.methods) {
val isStatic = Opcodes.ACC_STATIC in method.access
var currentIndex = if (isStatic) 0 else 1
val isStatic = access and Opcodes.ACC_STATIC != 0
var currentIndex = if (isStatic) 0 else 1
val types = Type.getArgumentTypes(method.desc)
if (types.isEmpty()) {
continue
}
val types = Type.getArgumentTypes(descriptor)
if (types.isEmpty()) {
return ret
}
val params = ArrayList<ParamTarget>(types.size)
val data = Data(node.name, method.name, method.desc, params)
methods += data
val params = ArrayList<ParamTarget>(types.size)
val data = Data(className, name, descriptor, params)
methods += data
for (i in types.indices) {
params += ParamTarget(currentIndex, i)
currentIndex++
// Figure out if we should skip the next index
val type = types[i]
if (type === Type.LONG_TYPE || type === Type.DOUBLE_TYPE) {
for (i in types.indices) {
params += ParamTarget(currentIndex, i)
currentIndex++
// Figure out if we should skip the next index
val type = types[i]
if (type === Type.LONG_TYPE || type === Type.DOUBLE_TYPE) {
currentIndex++
}
}
}
return ret
}
}
@ -303,45 +280,20 @@ object ParamIndexes {
* can handle it in our generated mappings.
*/
object SyntheticMethods {
class Visitor(
classVisitor: ClassVisitor?,
private val methods: MutableList<Data>
) : BaseClassVisitor(classVisitor) {
object Visitor : AsmUtil {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val ret = super.visitMethod(access, name, descriptor, signature, exceptions)
val className = currentClass ?: return ret
fun visit(node: ClassNode, methods: MutableList<Data>) {
for (method in node.methods) {
if (Opcodes.ACC_SYNTHETIC !in method.access || Opcodes.ACC_BRIDGE in method.access || method.name.contains('$')) {
continue
}
if (Opcodes.ACC_SYNTHETIC !in access || Opcodes.ACC_BRIDGE in access || name.contains('$')) {
return ret
}
val (baseName, baseDesc) = SyntheticUtil.findBaseMethod(method, node.name)
return SynthMethodVisitor(access, name, descriptor, signature, exceptions, className, methods)
}
}
private class SynthMethodVisitor(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<out String>?,
private val className: String,
private val methods: MutableList<Data>
) : MethodNode(Opcodes.ASM9, access, name, descriptor, signature, exceptions) {
override fun visitEnd() {
val (baseName, baseDesc) = SyntheticUtil.findBaseMethod(this, className)
if (baseName != name || baseDesc != desc) {
// Add this method as a synthetic for baseName
methods += Data(className, baseDesc, name, baseName)
if (baseName != method.name || baseDesc != method.desc) {
// Add this method as a synthetic for baseName
methods += Data(node.name, baseDesc, method.name, baseName)
}
}
}
}
@ -362,3 +314,68 @@ object SyntheticMethods {
)
}
}
/*
* There's no direct marker in bytecode to know if a
*/
object MethodOverrides {
object Visitor : AsmUtil {
fun visit(
node: ClassNode,
classNodeCache: ClassNodeCache,
methodOverrides: SimpleDirectedGraph<MethodRef, DefaultEdge>
) {
val superMethods = collectSuperMethods(node, classNodeCache)
val disqualifiedMethods = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
for (method in node.methods) {
if (method.access in disqualifiedMethods) {
continue
}
if (method.name == "<init>" || method.name == "<clinit>") {
continue
}
val (name, desc) = SyntheticUtil.findBaseMethod(method, node.name)
superMethods[method.name + method.desc]?.let { className ->
val targetMethod = node.methods.firstOrNull { it.name == name && it.desc == desc } ?: method
val methodRef = MethodRef(node.name, method.name, method.desc)
val targetMethodRef = MethodRef(className, targetMethod.name, targetMethod.desc)
methodOverrides.addVertex(methodRef)
methodOverrides.addVertex(targetMethodRef)
methodOverrides.addEdge(methodRef, targetMethodRef)
}
}
}
private fun collectSuperMethods(node: ClassNode, classNodeCache: ClassNodeCache): Map<String, String> {
fun collectSuperMethods(node: ClassNode, superMethods: HashMap<String, String>) {
val supers = listOfNotNull(node.superName, *node.interfaces.toTypedArray())
if (supers.isEmpty()) {
return
}
val disqualifiedMethods = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
val superNodes = supers.mapNotNull { classNodeCache.findClass(it) }
for (superNode in superNodes) {
superNode.methods.asSequence()
.filter { method -> method.access !in disqualifiedMethods }
.filter { method -> method.name != "<init>" && method.name != "<clinit>" }
.map { method -> method.name + method.desc to superNode.name }
.toMap(superMethods)
}
for (superNode in superNodes) {
collectSuperMethods(superNode, superMethods)
}
}
val result = hashMapOf<String, String>()
collectSuperMethods(node, result)
return result
}
}
}

View file

@ -0,0 +1,53 @@
package io.papermc.paperweight.util
import java.util.jar.JarFile
import java.util.zip.ZipEntry
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
class ClassNodeCache(private val jarFile: JarFile, private val fallbackJar: JarFile? = null) {
private val classNodeMap = hashMapOf<String, ClassNode?>()
fun findClass(name: String): ClassNode? {
return classNodeMap.computeIfAbsent(normalize(name)) { fileName ->
val classData = findClassData(fileName) ?: return@computeIfAbsent null
val classReader = ClassReader(classData)
val node = ClassNode(Opcodes.ASM9)
classReader.accept(node, 0)
return@computeIfAbsent node
}
}
private fun findClassData(className: String): ByteArray? {
val entry = ZipEntry(className)
return (
jarFile.getInputStream(entry) // remapped class
?: fallbackJar?.getInputStream(entry) // library class
?: ClassLoader.getSystemResourceAsStream(className) // JDK class
)?.use { it.readBytes() }
}
private fun normalize(name: String): String {
var workingName = name
if (workingName.endsWith(".class")) {
workingName = workingName.substring(0, workingName.length - 6)
}
var startIndex = 0
var endIndex = workingName.length
if (workingName.startsWith('L')) {
startIndex = 1
}
if (workingName.endsWith(';')) {
endIndex--
}
return workingName.substring(startIndex, endIndex).replace('.', '/') + ".class"
}
fun clear() {
classNodeMap.clear()
}
}

View file

@ -0,0 +1,40 @@
package io.papermc.paperweight.util
import org.cadixdev.bombe.type.signature.MethodSignature
import org.jgrapht.Graph
import org.jgrapht.graph.DefaultEdge
import org.jgrapht.graph.SimpleDirectedGraph
import org.jgrapht.nio.dot.DOTImporter
import org.jgrapht.nio.gml.GmlExporter
import org.jgrapht.nio.gml.GmlImporter
import org.jgrapht.nio.json.JSONExporter
import org.jgrapht.nio.json.JSONImporter
data class MethodRef(val className: String, val methodName: String, val methodDesc: String) {
companion object {
fun from(className: String, sig: MethodSignature): MethodRef {
return MethodRef(className, sig.name, sig.descriptor.toString())
}
}
}
fun writeOverrides(graph: Graph<MethodRef, DefaultEdge>, file: Any) {
val exporter = JSONExporter<MethodRef, DefaultEdge> { ref -> "${ref.className} ${ref.methodName} ${ref.methodDesc}" }
file.convertToFile().bufferedWriter().use { writer ->
exporter.exportGraph(graph, writer)
}
}
fun readOverrides(file: Any): SimpleDirectedGraph<MethodRef, *> {
val importer = JSONImporter<MethodRef, DefaultEdge>()
importer.setVertexFactory {
val (className, methodName, methodDesc) = it.split(" ")
MethodRef(className, methodName, methodDesc)
}
val overrides = SimpleDirectedGraph<MethodRef, DefaultEdge>(DefaultEdge::class.java)
file.convertToFile().bufferedReader().use { reader ->
importer.importGraph(overrides, reader)
}
return overrides
}

View file

@ -168,16 +168,21 @@ fun <T> emptyMergeResult(): MergeResult<T?> {
}
// We have to create our own task delegate because the ones Gradle provides don't work in plugin dev environments
inline fun <reified T : Task> TaskContainer.registering(noinline configure: T.() -> Unit): TaskDelegateProvider<T> {
inline fun <reified T : Task> TaskContainer.registering(noinline configure: (T.() -> Unit)? = null): TaskDelegateProvider<T> {
return TaskDelegateProvider(this, T::class, configure)
}
class TaskDelegateProvider<T : Task>(
private val container: TaskContainer,
private val type: KClass<T>,
private val configure: T.() -> Unit
private val configure: (T.() -> Unit)?
) {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): TaskDelegate<T> {
val provider = container.register(property.name, type.java, configure)
val provider = if (configure != null) {
container.register(property.name, type.java, configure)
} else {
container.register(property.name, type.java) {
}
}
return TaskDelegate(provider)
}
}