Run paperclip patch process in separate process

This commit is contained in:
Kyle Wood 2021-01-02 23:06:31 -08:00
parent c4eb91fe8f
commit 2c4b1e1409
4 changed files with 123 additions and 79 deletions

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)
}

View file

@ -71,8 +71,8 @@ abstract class GenerateMappings : DefaultTask() {
@get:OutputFile
abstract val outputMappings: RegularFileProperty
// @get:Inject
// abstract val workerExecutor: WorkerExecutor
@get:Inject
abstract val workerExecutor: WorkerExecutor
@TaskAction
fun run() {
@ -94,7 +94,6 @@ abstract class GenerateMappings : DefaultTask() {
).merge()
// Fill out any missing inheritance info in the mappings
/*
val tempMappingsFile = Files.createTempFile("mappings", "tiny")
val tempMappingsOutputFile = Files.createTempFile("mappings-out", "tiny")
@ -118,10 +117,9 @@ abstract class GenerateMappings : DefaultTask() {
Files.deleteIfExists(tempMappingsFile)
Files.deleteIfExists(tempMappingsOutputFile)
}
*/
ensureParentExists(outputMappings)
MappingFormats.TINY.write(merged, outputMappings.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
MappingFormats.TINY.write(filledMerged, outputMappings.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
}
abstract class AtlasAction : WorkAction<AtlasParameters> {

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

@ -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)
}
}