Initial work on userdev

This commit is contained in:
Kyle Wood 2021-06-14 02:19:58 -05:00 committed by Jason
parent 54e712a6f6
commit 24a8b06ad2
15 changed files with 539 additions and 32 deletions

View file

@ -92,6 +92,24 @@ class PaperweightCore : Plugin<Project> {
}
target.afterEvaluate {
target.repositories {
maven(extension.paramMappingsRepo) {
content {
onlyForConfigurations(Constants.PARAM_MAPPINGS_CONFIG)
}
}
maven(extension.remapRepo) {
content {
onlyForConfigurations(Constants.REMAPPER_CONFIG)
}
}
maven(extension.decompileRepo) {
content {
onlyForConfigurations(Constants.DECOMPILER_CONFIG)
}
}
}
// Setup the server jar
val cache = target.layout.cache

View file

@ -42,6 +42,10 @@ open class PaperweightCoreExtension(objects: ObjectFactory, layout: ProjectLayou
val mcDevSourceDir: DirectoryProperty = objects.directoryProperty().convention(serverProject.map { it.layout.cacheDir(MC_DEV_SOURCES_DIR) })
val paramMappingsRepo: Property<String> = objects.property()
val decompileRepo: Property<String> = objects.property()
val remapRepo: Property<String> = objects.property()
@Suppress("MemberVisibilityCanBePrivate")
val craftBukkit = CraftBukkitExtension(objects, workDir)
val spigot = SpigotExtension(objects, workDir)

View file

@ -156,4 +156,32 @@ open class AllTasks(
outputMappings.set(cache.resolve(PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS))
}
@Suppress("unused")
val generateDevelopmentBundle by tasks.registering<GenerateDevBundle> {
// dependsOn(applyPatches)
decompiledJar.set(decompileJar.flatMap { it.outputJar }.get())
sourceDir.set(extension.paper.paperServerDir.map { it.dir("src/main/java") }.get())
minecraftVersion.set(extension.minecraftVersion)
buildDataDir.set(extension.craftBukkit.buildDataDir)
spigotClassMappingsFile.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.classMappings }))
spigotMemberMappingsFile.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.memberMappings }))
spigotAtFile.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.accessTransforms }))
paramMappingsUrl.set(extension.paramMappingsRepo)
decompilerUrl.set(extension.paramMappingsRepo)
remapperUrl.set(extension.paramMappingsRepo)
paramMappingsConfig.set(project.configurations.named(Constants.PARAM_MAPPINGS_CONFIG))
decompilerConfig.set(project.configurations.named(Constants.DECOMPILER_CONFIG))
remapperConfig.set(project.configurations.named(Constants.REMAPPER_CONFIG))
additionalSpigotClassMappingsFile.set(extension.paper.additionalSpigotClassMappings)
additionalSpigotMemberMappingsFile.set(extension.paper.additionalSpigotMemberMappings)
mappingsPatchFile.set(extension.paper.mappingsPatch)
devBundleFile.set(project.layout.buildDirectory.map { it.file("libs/paperDevBundle-${project.version}.zip") })
}
}

View file

@ -53,7 +53,7 @@ abstract class ApplyRawDiffPatches : ZippedTask() {
Git.checkForGit()
val input = inputDir.path
input.copyRecursively(rootDir)
input.copyRecursivelyTo(rootDir)
val patches = patchDir.pathOrNull ?: return
val patchSet = patches.useDirectoryEntries("*.patch") { it.toMutableList() }

View file

@ -0,0 +1,321 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2021 Kyle Wood (DemonWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Git
import io.papermc.paperweight.util.copyRecursivelyTo
import io.papermc.paperweight.util.deleteForcefully
import io.papermc.paperweight.util.deleteRecursively
import io.papermc.paperweight.util.directory
import io.papermc.paperweight.util.gson
import io.papermc.paperweight.util.openZip
import io.papermc.paperweight.util.path
import io.papermc.paperweight.util.pathOrNull
import io.papermc.paperweight.util.writeZip
import java.io.ByteArrayOutputStream
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemLocationProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class GenerateDevBundle : DefaultTask() {
@get:InputFile
abstract val decompiledJar: RegularFileProperty
@get:InputDirectory
abstract val sourceDir: DirectoryProperty
@get:Input
abstract val minecraftVersion: Property<String>
// Spigot configuration - start
@get:InputDirectory
abstract val buildDataDir: DirectoryProperty
@get:InputFile
abstract val spigotClassMappingsFile: RegularFileProperty
@get:InputFile
abstract val spigotMemberMappingsFile: RegularFileProperty
@get:InputFile
abstract val spigotAtFile: RegularFileProperty
// Spigot configuration - end
// Paper configuration - start
@get:Input
abstract val paramMappingsUrl: Property<String>
@get:Classpath
abstract val paramMappingsConfig: Property<Configuration>
@get:Input
abstract val decompilerUrl: Property<String>
@get:Classpath
abstract val decompilerConfig: Property<Configuration>
@get:Input
abstract val remapperUrl: Property<String>
@get:Classpath
abstract val remapperConfig: Property<Configuration>
@get:Optional
@get:InputFile
abstract val additionalSpigotClassMappingsFile: RegularFileProperty
@get:Optional
@get:InputFile
abstract val additionalSpigotMemberMappingsFile: RegularFileProperty
@get:Optional
@get:InputFile
abstract val mappingsPatchFile: RegularFileProperty
// Paper configuration - end
@get:OutputFile
abstract val devBundleFile: RegularFileProperty
@TaskAction
fun run() {
val devBundle = devBundleFile.path
devBundle.deleteForcefully()
devBundle.parent.createDirectories()
val tempPatchDir = createTempDirectory("devBundlePatches")
try {
generatePatches(tempPatchDir)
val dataDir = "data"
val patchesDir = "patches"
val config = createBundleConfig(dataDir, patchesDir)
devBundle.writeZip().use { zip ->
zip.getPath("config.json").bufferedWriter(Charsets.UTF_8).use { writer ->
gson.toJson(config, writer)
}
val dataZip = zip.getPath(dataDir)
dataZip.createDirectories()
additionalSpigotClassMappingsFile.pathIfExists?.copyTo(dataZip.resolve(additionalSpigotClassMappingsFileName))
additionalSpigotMemberMappingsFile.pathIfExists?.copyTo(dataZip.resolve(additionalSpigotMemberMappingsFileName))
mappingsPatchFile.pathIfExists?.copyTo(dataZip.resolve(mappingsPatchFileName))
val patchesZip = zip.getPath(patchesDir)
tempPatchDir.copyRecursivelyTo(patchesZip)
}
} finally {
tempPatchDir.deleteRecursively()
}
}
private fun generatePatches(output: Path) {
val rootSource = sourceDir.path
Files.walk(rootSource).use { stream ->
decompiledJar.path.openZip().use { decompJar ->
val decompRoot = decompJar.rootDirectories.single()
for (file in stream) {
if (file.isDirectory()) {
continue
}
val relativeFile = file.relativeTo(rootSource)
val relativeFilePath = relativeFile.invariantSeparatorsPathString
val decompFile = decompRoot.resolve(relativeFilePath)
if (decompFile.notExists()) {
val outputFile = output.resolve(relativeFilePath)
outputFile.parent.createDirectories()
file.copyTo(outputFile)
} else {
val diffText = diffFiles(relativeFilePath, decompFile, file)
val patchName = relativeFile.name.substringBeforeLast('.') + ".patch"
val outputFile = output.resolve(relativeFilePath).resolveSibling(patchName)
outputFile.parent.createDirectories()
outputFile.writeText(diffText)
}
}
}
}
}
private fun diffFiles(fileName: String, original: Path, patched: Path): String {
val dir = createTempDirectory("diff")
try {
val oldFile = dir.resolve("old.java")
val newFile = dir.resolve("new.java")
original.copyTo(oldFile)
patched.copyTo(newFile)
val args = listOf(
"diff",
"--color=never",
"-ud",
"--label", "a/$fileName",
oldFile.absolutePathString(),
"--label", "b/$fileName",
newFile.absolutePathString(),
)
return runDiff(dir, args)
} finally {
dir.deleteRecursively()
}
}
private fun runDiff(dir: Path, args: List<String>): String {
val process = ProcessBuilder(args)
.directory(dir)
.redirectErrorStream(true)
.start()
val out = ByteArrayOutputStream()
process.inputStream.use { input ->
input.copyTo(out)
}
return String(out.toByteArray(), Charset.defaultCharset())
.replace(System.getProperty("line.separator"), "\n")
}
@Suppress("SameParameterValue")
private fun createBundleConfig(dataTargetDir: String, patchTargetDir: String): DevBundleConfig {
return DevBundleConfig(
minecraftVersion = minecraftVersion.get(),
spigotData = createSpigotConfig(),
buildData = createBuildDataConfig(dataTargetDir),
decompile = createDecompileRunner(),
remap = createRemapRunner(),
patchDir = patchTargetDir
)
}
private fun createSpigotConfig(): SpigotData {
val dir = buildDataDir.path
val git = Git(dir)
val remoteUrl = git("remote", "get-url", "origin").getText().trim()
val commitRef = git("rev-parse", "HEAD").getText().trim()
val classMappings = spigotClassMappingsFile.path.relativeTo(dir).toString()
val memberMappings = spigotMemberMappingsFile.path.relativeTo(dir).toString()
val at = spigotAtFile.path.relativeTo(dir).toString()
return SpigotData(
ref = commitRef,
checkoutUrl = remoteUrl,
classMappingsFile = classMappings,
memberMappingsFile = memberMappings,
atFile = at
)
}
private fun createBuildDataConfig(targetDir: String): BuildData {
return BuildData(
paramMappings = determineMavenDep(paramMappingsUrl, paramMappingsConfig),
additionalSpigotClassMappingsFile = additionalSpigotClassMappingsFile.ifExists("$targetDir/$additionalSpigotClassMappingsFileName"),
additionalSpigotMemberMappingsFile = additionalSpigotMemberMappingsFile.ifExists("$targetDir/$additionalSpigotMemberMappingsFileName"),
mappingsPatchFile = mappingsPatchFile.ifExists("$targetDir/$mappingsPatchFileName")
)
}
private fun determineMavenDep(url: Provider<String>, configuration: Provider<Configuration>): MavenDep {
return MavenDep(url.get(), determineArtifactCoordinates(configuration.get()))
}
private fun determineArtifactCoordinates(configuration: Configuration): List<String> {
return configuration.dependencies.map { dep ->
(dep.group ?: error("no group: $dep")) + ":" + dep.name + ":" + (dep.version ?: error("no version: $dep"))
}
}
private fun createDecompileRunner(): Runner {
return Runner(
dep = determineMavenDep(decompilerUrl, decompilerConfig),
args = forgeFlowerArgList()
)
}
private fun createRemapRunner(): Runner {
return Runner(
dep = determineMavenDep(remapperUrl, remapperConfig),
args = tinyRemapperArgsList()
)
}
private fun FileSystemLocationProperty<*>.ifExists(text: String): String? {
if (!this.isPresent) {
return null
}
if (this.path.notExists()) {
return null
}
return text
}
private val FileSystemLocationProperty<*>.pathIfExists: Path?
get() = pathOrNull?.takeIf { it.exists() }
data class DevBundleConfig(
val minecraftVersion: String,
val spigotData: SpigotData,
val buildData: BuildData,
val decompile: Runner,
val remap: Runner,
val patchDir: String
)
data class BuildData(
val paramMappings: MavenDep,
val additionalSpigotClassMappingsFile: String?,
val additionalSpigotMemberMappingsFile: String?,
val mappingsPatchFile: String?
)
data class SpigotData(val ref: String, val checkoutUrl: String, val classMappingsFile: String, val memberMappingsFile: String, val atFile: String)
data class MavenDep(val url: String, val coordinates: List<String>)
data class Runner(val dep: MavenDep, val args: List<String>)
companion object {
const val additionalSpigotClassMappingsFileName = "additional-spigot-class-mappings.csrg"
const val additionalSpigotMemberMappingsFileName = "additional-spigot-member-mappings.csrg"
const val mappingsPatchFileName = "mappings-patch.tiny"
}
}

View file

@ -24,6 +24,7 @@ package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import io.papermc.paperweight.PaperweightException
import kotlin.io.path.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
@ -31,6 +32,49 @@ import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
fun tinyRemapperArgsList(): List<String> {
return listOf(
"{input}",
"{output}",
"{mappings}",
"{from}",
"{to}",
"{classpath}",
"--fixpackageaccess",
"--renameinvalidlocals",
"--threads=1",
"--rebuildsourcefilenames"
)
}
fun createTinyRemapperArgs(
input: String,
output: String,
mappings: String,
from: String,
to: String,
classpath: Array<String>
): List<String> {
val result = mutableListOf<String>()
for (arg in tinyRemapperArgsList()) {
val mapped = when (arg) {
"{input}" -> input
"{output}" -> output
"{mappings}" -> mappings
"{from}" -> from
"{to}" -> to
"{classpath}" -> classpath
else -> arg
}
when (mapped) {
is String -> result += mapped
is Array<*> -> mapped.mapTo(result) { it as? String ?: throw PaperweightException("Expected String! Got: '$it'.") }
else -> throw PaperweightException("Don't know what to do with '$mapped'!")
}
}
return result
}
@CacheableTask
abstract class RemapJar : JavaLauncherTask() {
@ -69,9 +113,7 @@ abstract class RemapJar : JavaLauncherTask() {
super.init()
outputJar.convention(defaultOutput())
singleThreaded.convention(true)
jvmargs.convention(listOf("-Xmx1G"))
rebuildSourceFilenames.convention(true)
}
@TaskAction
@ -79,22 +121,14 @@ abstract class RemapJar : JavaLauncherTask() {
val logFile = layout.cache.resolve(paperTaskOutput("log"))
ensureDeleted(logFile)
val args = mutableListOf(
val args = createTinyRemapperArgs(
inputJar.path.absolutePathString(),
outputJar.path.absolutePathString(),
mappingsFile.path.absolutePathString(),
fromNamespace.get(),
toNamespace.get(),
*remapClasspath.asFileTree.map { it.absolutePath }.toTypedArray(),
"--fixpackageaccess",
"--renameinvalidlocals"
remapClasspath.asFileTree.map { it.absolutePath }.toTypedArray()
)
if (singleThreaded.get()) {
args += "--threads=1"
}
if (rebuildSourceFilenames.get()) {
args += "--rebuildsourcefilenames"
}
ensureParentExists(logFile)
launcher.runJar(remapper, layout.cache, logFile, jvmArgs = jvmargs.get(), args = args.toTypedArray())

View file

@ -35,6 +35,37 @@ import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
fun forgeFlowerArgList(): List<String> {
return listOf(
"-ind= ",
"-din=1",
"-rbr=1",
"-dgs=1",
"-asc=1",
"-rsy=1",
"-iec=1",
"-jvn=0",
"-isl=0",
"-iib=1",
"-log=TRACE",
"-cfg",
"{libraries}",
"{input}",
"{output}"
)
}
fun createForgeFlowerArgs(libraries: String, input: String, output: String): List<String> {
return forgeFlowerArgList().map {
when (it) {
"{libraries}" -> libraries
"{input}" -> input
"{output}" -> output
else -> it
}
}
}
@CacheableTask
abstract class RunForgeFlower : JavaLauncherTask() {
@ -82,19 +113,7 @@ abstract class RunForgeFlower : JavaLauncherTask() {
}
}
val argList = listOf(
"-ind= ",
"-din=1",
"-rbr=1",
"-dgs=1",
"-asc=1",
"-rsy=1",
"-iec=1",
"-jvn=0",
"-isl=0",
"-iib=1",
"-log=TRACE",
"-cfg",
val argList = createForgeFlowerArgs(
tempFile.absolutePathString(),
inputJar.path.absolutePathString(),
target.absolutePathString()

View file

@ -267,7 +267,7 @@ abstract class RemapPatches : BaseTask() {
if (recreate) {
deleteRecursively()
createDirectories()
source?.copyRecursively(this)
source?.copyRecursivelyTo(this)
}
}
}

View file

@ -106,14 +106,14 @@ private fun Path.fixWindowsPermissionsForDeletion() {
}
}
fun Path.copyRecursively(target: Path) {
fun Path.copyRecursivelyTo(target: Path) {
target.createDirectories()
if (!exists()) {
return
}
Files.walk(this).use { stream ->
stream.forEach { f ->
val targetPath = target.resolve(f.relativeTo(this))
for (f in stream) {
val targetPath = target.resolve(f.relativeTo(this).invariantSeparatorsPathString)
if (f.isDirectory()) {
targetPath.createDirectories()
} else {

View file

@ -57,7 +57,7 @@ import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.*
val gson: Gson = GsonBuilder().setPrettyPrinting().registerTypeHierarchyAdapter(Path::class.java, PathJsonConverter()).create()
val gson: Gson = GsonBuilder().disableHtmlEscaping().setPrettyPrinting().registerTypeHierarchyAdapter(Path::class.java, PathJsonConverter()).create()
class PathJsonConverter : JsonDeserializer<Path?>, JsonSerializer<Path?> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Path? {

View file

@ -0,0 +1,9 @@
plugins {
`config-kotlin`
`config-publish`
}
dependencies {
shade(projects.paperweightLib)
implementation(libs.kotson)
}

View file

@ -0,0 +1,48 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2021 Kyle Wood (DemonWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.userdev
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.util.Constants
import io.papermc.paperweight.util.cache
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.Delete
import org.gradle.kotlin.dsl.*
class PaperweightUser : Plugin<Project> {
override fun apply(target: Project) {
val userdev = target.extensions.create(Constants.EXTENSION, PaperweightUserExtension::class)
target.gradle.sharedServices.registerIfAbsent("download", DownloadService::class) {}
target.tasks.register<Delete>("cleanCache") {
group = "paperweight"
description = "Delete the project setup cache and task outputs."
delete(target.layout.cache)
}
target.configurations.create(Constants.REMAPPER_CONFIG)
}
}

View file

@ -0,0 +1,25 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2021 Kyle Wood (DemonWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.userdev
class PaperweightUserExtension

View file

@ -0,0 +1 @@
implementation-class=io.papermc.paperweight.userdev.PaperweightUser

View file

@ -1,6 +1,6 @@
rootProject.name = "paperweight"
include("paperweight-core", "paperweight-lib", "paperweight-patcher")
include("paperweight-core", "paperweight-lib", "paperweight-patcher", "paperweight-userdev")
enableFeaturePreview("VERSION_CATALOGS")
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")