import groovy.json.JsonBuilder import groovy.json.JsonSlurper import java.util.Locale plugins { java id("org.leavesmc.leavesweight.patcher") version "2.1.0-SNAPSHOT" } subprojects { apply(plugin = "java-library") apply(plugin = "maven-publish") extensions.configure { toolchain { languageVersion = JavaLanguageVersion.of(21) vendor = JvmVendorSpec.ADOPTIUM } } repositories { mavenCentral() maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.leavesmc.org/releases") { content { onlyForConfigurations("leavesclip") } } } tasks.withType().configureEach { isPreserveFileTimestamps = false isReproducibleFileOrder = true } tasks.withType { options.encoding = Charsets.UTF_8.name() options.release = 21 options.isFork = true options.forkOptions.memoryMaximumSize = "6g" } tasks.withType { options.encoding = Charsets.UTF_8.name() } tasks.withType { filteringCharset = Charsets.UTF_8.name() } extensions.configure { repositories { maven("https://repo.leavesmc.org/snapshots") { name = "leaves" credentials(PasswordCredentials::class) { username = System.getenv("LEAVES_USERNAME") password = System.getenv("LEAVES_PASSWORD") } } } } } paperweight { upstreams.paper { ref = providers.gradleProperty("paperRef") patchFile { path = "paper-server/build.gradle.kts" outputFile = file("leaves-server/build.gradle.kts") patchFile = file("leaves-server/build.gradle.kts.patch") } patchFile { path = "paper-api/build.gradle.kts" outputFile = file("leaves-api/build.gradle.kts") patchFile = file("leaves-api/build.gradle.kts.patch") } patchDir("paperApi") { upstreamPath = "paper-api" excludes = setOf("build.gradle.kts") patchesDir = file("leaves-api/paper-patches") outputDir = file("paper-api") } } } val patchTasks = listOf( "::applyPaperApiPatches" to "::rebuildPaperApiPatches", "::applyPaperSingleFilePatches" to "::rebuildPaperSingleFilePatches", ":leaves-server:applyMinecraftPatches" to ":leaves-server:rebuildMinecraftPatches", ":leaves-server:applyPaperServerPatches" to ":leaves-server:rebuildPaperServerPatches" ) val patchDir = listOf( "paper-api", "leaves-server", "leaves-server/src/minecraft/java", "paper-server", ) val statusFile = layout.buildDirectory.file("patchTaskStatus.json").get().asFile fun readTaskStatus(): Map { if (!statusFile.exists()) { return emptyMap() } try { @Suppress("UNCHECKED_CAST") return JsonSlurper().parse(statusFile) as Map } catch (_: Exception) { return emptyMap() } } fun writeTaskStatus(status: Map) { statusFile.writeText(JsonBuilder(status).toPrettyString()) } fun executeCommand(command: List, directory: File): Boolean { try { val processBuilder = ProcessBuilder(command) processBuilder.directory(directory) val process = processBuilder.start() val outputThread = Thread { process.inputStream.bufferedReader().use { reader -> var line: String? while (reader.readLine().also { line = it } != null) { println(line) } } } val errorThread = Thread { process.errorStream.bufferedReader().use { reader -> var line: String? while (reader.readLine().also { line = it } != null) { System.err.println(line) } } } outputThread.start() errorThread.start() val exitCode = process.waitFor() outputThread.join() errorThread.join() return exitCode == 0 } catch (_: Exception) { return false } } fun hasGitChanges(directory: File): Boolean { val process = ProcessBuilder("git", "status", "--porcelain").directory(directory).start() val output = process.inputStream.bufferedReader().readText() process.waitFor() return output.isNotEmpty() } fun executeTask(taskPath: String): Boolean { val parts = taskPath.split(":") val projectPath = if (parts.size > 2) parts.subList(0, parts.size - 1).joinToString(":") else ":" val taskName = parts.last() val fullTaskPath = if (projectPath == ":" || projectPath.isEmpty()) taskName else "$projectPath:$taskName" val gradlew = if (System.getProperty("os.name").lowercase(Locale.getDefault()).contains("windows")) "${project.rootDir}\\gradlew.bat" else "${project.rootDir}/gradlew" if (!executeCommand(listOf(gradlew, fullTaskPath), project.projectDir)) { throw GradleException("Task $fullTaskPath FAILED") } return true } tasks.register("applyAllPatchesSequentially") { group = "leaves" description = "Apply all patches sequentially, run rebuild after success, wait for manual fix if failed" doFirst { println("๐Ÿš€ Starting sequential patch application...") } doLast { val taskStatus = readTaskStatus().toMutableMap() var currentIndex = 0 while (currentIndex < patchTasks.size) { val (applyTaskPath, rebuildTaskPath) = patchTasks[currentIndex] if (taskStatus[applyTaskPath] == "COMPLETED") { println("โฉ Skipping completed task: $applyTaskPath") currentIndex++ continue } if (taskStatus[applyTaskPath] == "FAILED") { println("โš ๏ธ Detected previous failure of $applyTaskPath, running $rebuildTaskPath first") try { executeTask(rebuildTaskPath) println("โœ… $rebuildTaskPath completed successfully") } catch (e: Exception) { taskStatus[applyTaskPath] = "FAILED" writeTaskStatus(taskStatus) throw GradleException("$rebuildTaskPath failed: ${e.message}", e) } } println("๐Ÿ”„ Running $applyTaskPath...") try { executeTask(applyTaskPath) println("โœ… $applyTaskPath completed successfully") println("๐Ÿ”„ Running $rebuildTaskPath after successful apply...") try { executeTask(rebuildTaskPath) println("โœ… $rebuildTaskPath completed successfully") } catch (e: Exception) { println("โš ๏ธ $rebuildTaskPath failed, but continuing with next tasks: ${e.message}") } taskStatus[applyTaskPath] = "COMPLETED" writeTaskStatus(taskStatus) currentIndex++ } catch (e: Exception) { taskStatus[applyTaskPath] = "FAILED" writeTaskStatus(taskStatus) throw GradleException("$applyTaskPath failed, please fix the issues and run this task again", e) } } if (currentIndex >= patchTasks.size) { tasks.named("resetPatchTaskStatus").get().actions.forEach { it.execute(tasks.named("resetPatchTaskStatus").get()) } println("โœจ All patch tasks completed successfully!") } } } tasks.register("applyNextPatch") { group = "leaves" description = "Apply the next patch" doLast { val taskStatus = readTaskStatus().toMutableMap() val failedIndex = patchTasks.indexOfFirst { (applyTaskPath, _) -> taskStatus[applyTaskPath] == "FAILED" } if (failedIndex >= 0) { val directory = project.projectDir.resolve(patchDir[failedIndex]) executeCommand(listOf("git", "add", "."), directory) val gitCommand = if (hasGitChanges(directory)) { listOf("git", "am", "--continue") } else { listOf("git", "am", "--skip") } executeCommand(gitCommand, directory) } } } tasks.register("resetPatchTaskStatus") { group = "leaves" description = "Reset the status of all patch tasks" doLast { if (statusFile.exists()) { statusFile.delete() println("๐Ÿงน All patch task statuses have been reset") } } } tasks.register("showPatchTaskStatus") { group = "leaves" description = "Show the current status of all patch tasks" doLast { val status = readTaskStatus() println("๐Ÿ“Š Patch Task Status:") if (status.isEmpty()) { println(" No tasks executed yet or status has been reset") } else { patchTasks.forEach { (applyTask, _) -> val taskStatus = status[applyTask] ?: "PENDING" val statusIcon = when (taskStatus) { "COMPLETED" -> "โœ…" "FAILED" -> "โŒ" else -> "โณ" } println(" $statusIcon $applyTask: $taskStatus") } } } }