From 43f2c0690944fd4c21536876602667323b816fc8 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 13:52:22 -0800 Subject: [PATCH 01/10] Rewrite userdev setup pipeline Uses a graph for ordering steps instead of manual ordering, improved input/output hashing and up-to-date checking. Except for the final outputs which are keyed to the dev bundle, steps are independent of the dev bundle and can be reused between bundles that have the same inputs for the step. For example when using a Paper and Folia 1.21.4 dev bundle in two projects, decompile will only run once as only the final apply dev bundle patches step differs. Final outputs are copied into a project local location to avoid projects sharing a bundle from having a task with a shared output location. --- .gitignore | 4 - .../core/tasks/ImportLibraryFiles.kt | 55 +-- .../io/papermc/paperweight/DownloadService.kt | 8 - .../paperweight/tasks/mache/DecompileJar.kt | 4 +- .../paperweight/tasks/mache/RemapJar.kt | 2 +- .../io/papermc/paperweight/util/file-lock.kt | 143 ++++++++ .../io/papermc/paperweight/util/file.kt | 54 +-- .../io/papermc/paperweight/util/jar-runner.kt | 8 +- .../io/papermc/paperweight/util/utils.kt | 69 +++- paperweight-userdev/build.gradle.kts | 1 + .../paperweight/userdev/PaperweightUser.kt | 95 +++--- .../userdev/internal/action/CacheCleaner.kt | 70 ++++ .../userdev/internal/action/WorkDispatcher.kt | 51 +++ .../internal/action/WorkDispatcherImpl.kt | 131 ++++++++ .../userdev/internal/action/WorkGraph.kt | 214 ++++++++++++ .../userdev/internal/action/values.kt | 165 +++++++++ .../{ExtractDevBundle.kt => DevBundles.kt} | 46 +-- .../userdev/internal/setup/SetupHandler.kt | 41 ++- .../internal/setup/SetupHandlerImpl.kt | 241 +++++++------- .../userdev/internal/setup/UserdevSetup.kt | 37 +- .../internal/setup/UserdevSetupTask.kt | 39 ++- .../action/AccessTransformMinecraftAction.kt | 63 ++++ .../action/ApplyDevBundlePatchesAction.kt | 114 +++++++ .../setup/action/DecompileMinecraftAction.kt | 59 ++++ .../ExtractFromBundlerAction.kt} | 45 +-- .../FilterPaperShadowJarAction.kt} | 38 +-- .../FilterVanillaJarAction.kt} | 28 +- .../FixMinecraftJarAction.kt} | 40 +-- .../setup/action/GenerateMappingsAction.kt | 57 ++++ .../setup/action/RemapMinecraftAction.kt | 64 ++++ .../setup/action/RemapMinecraftMacheAction.kt | 68 ++++ .../RunPaperclipAction.kt} | 63 ++-- .../setup/action/SetupMacheSourcesAction.kt | 90 +++++ .../setup/action/VanillaServerDownloads.kt | 97 ++++++ .../setup/step/ApplyDevBundlePatches.kt | 119 ------- .../internal/setup/step/DecompileMinecraft.kt | 77 ----- .../setup/step/ExtractFromBundlerStep.kt | 58 ---- .../setup/step/GenerateMappingsStep.kt | 75 ----- .../setup/step/MinecraftSourcesMache.kt | 104 ------ .../internal/setup/step/RemapMinecraft.kt | 91 ----- .../setup/step/RemapMinecraftMache.kt | 104 ------ .../internal/setup/step/VanillaSteps.kt | 78 ----- .../userdev/internal/setup/step/steps.kt | 137 -------- .../userdev/internal/setup/util/utils.kt | 236 ------------- .../internal/setup/v2/SetupHandlerImplV2.kt | 315 ++++++++++-------- .../internal/setup/v5/SetupHandlerImplV5.kt | 298 +++++++++-------- .../userdev/internal/util/utils.kt | 118 +++++++ .../internal/action/WorkDispatcherTest.kt | 137 ++++++++ 48 files changed, 2440 insertions(+), 1811 deletions(-) create mode 100644 paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcher.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt rename paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/{ExtractDevBundle.kt => DevBundles.kt} (63%) create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/AccessTransformMinecraftAction.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ApplyDevBundlePatchesAction.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/DecompileMinecraftAction.kt rename paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/{step/AccessTransformMinecraft.kt => action/ExtractFromBundlerAction.kt} (51%) rename paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/{step/FilterPaperShadowJar.kt => action/FilterPaperShadowJarAction.kt} (75%) rename paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/{step/FilterVanillaJar.kt => action/FilterVanillaJarAction.kt} (57%) rename paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/{step/FixMinecraftJar.kt => action/FixMinecraftJarAction.kt} (51%) create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/GenerateMappingsAction.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftAction.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftMacheAction.kt rename paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/{step/RunPaperclip.kt => action/RunPaperclipAction.kt} (64%) create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/SetupMacheSourcesAction.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/VanillaServerDownloads.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ApplyDevBundlePatches.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/DecompileMinecraft.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ExtractFromBundlerStep.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/GenerateMappingsStep.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/MinecraftSourcesMache.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraft.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraftMache.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/VanillaSteps.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/steps.kt delete mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/util/utils.kt create mode 100644 paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/util/utils.kt create mode 100644 paperweight-userdev/src/test/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherTest.kt diff --git a/.gitignore b/.gitignore index 26be355ee..f5944d184 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,3 @@ ehthumbs_vista.db !gradle-wrapper.ja /.kotlin/ - -# todo remove again -/test/ -/testfork/ diff --git a/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/tasks/ImportLibraryFiles.kt b/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/tasks/ImportLibraryFiles.kt index 66582afec..922955c89 100644 --- a/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/tasks/ImportLibraryFiles.kt +++ b/paperweight-core/src/main/kotlin/io/papermc/paperweight/core/tasks/ImportLibraryFiles.kt @@ -30,7 +30,7 @@ import io.papermc.paperweight.util.constants.* import java.nio.file.Path import java.util.concurrent.ConcurrentHashMap import kotlin.io.path.* -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -63,21 +63,24 @@ abstract class IndexLibraryFiles : BaseTask() { @TaskAction fun run() { - val possible = findPossibleLibraryImports(libraries.sourcesJars()) - .groupBy { it.libraryFileName } - .mapValues { - it.value.map { v -> v.importFilePath } - } + val possible = ioDispatcher("IndexLibraryFiles").use { dispatcher -> + findPossibleLibraryImports(libraries.sourcesJars(), dispatcher) + .groupBy { it.libraryFileName } + .mapValues { + it.value.map { v -> v.importFilePath } + } + } + outputFile.path.cleanFile().outputStream().gzip().bufferedWriter().use { writer -> gson.toJson(possible, writer) } } - private fun findPossibleLibraryImports(libFiles: List): Collection = runBlocking { + private fun findPossibleLibraryImports(libFiles: List, dispatcher: CoroutineDispatcher): Collection = runBlocking { val found = ConcurrentHashMap.newKeySet() val suffix = ".java" libFiles.forEach { libFile -> - launch(Dispatchers.IO) { + launch(dispatcher) { libFile.openZipSafe().use { zipFile -> zipFile.walkSequence() .filter { it.isRegularFile() && it.name.endsWith(suffix) } @@ -125,14 +128,17 @@ abstract class ImportLibraryFiles : BaseTask() { gson.fromJson>>(reader, typeToken>>()) }.flatMap { entry -> entry.value.map { LibraryImport(entry.key, it) } }.toSet() val patchFiles = patches.files.flatMap { it.toPath().filesMatchingRecursive("*.patch") } - importLibraryFiles( - patchFiles, - devImports.pathOrNull, - outputDir.path, - libraries.sourcesJars(), - index, - true - ) + ioDispatcher("ImportLibraryFiles").use { dispatcher -> + importLibraryFiles( + patchFiles, + devImports.pathOrNull, + outputDir.path, + libraries.sourcesJars(), + index, + true, + dispatcher, + ) + } } } @@ -143,16 +149,17 @@ abstract class ImportLibraryFiles : BaseTask() { libFiles: List, index: Set, printOutput: Boolean, + dispatcher: CoroutineDispatcher, ) = runBlocking { // Import library classes - val allImports = findLibraryImports(importsFile, libFiles, index, patches) + val allImports = findLibraryImports(importsFile, libFiles, index, patches, dispatcher) val importsByLib = allImports.groupBy { it.libraryFileName } logger.log(if (printOutput) LogLevel.LIFECYCLE else LogLevel.DEBUG, "Importing {} classes from library sources...", allImports.size) for ((libraryFileName, imports) in importsByLib) { val libFile = libFiles.firstOrNull { it.name == libraryFileName } ?: throw PaperweightException("Failed to find library: $libraryFileName for classes ${imports.map { it.importFilePath }}") - launch(Dispatchers.IO) { + launch(dispatcher) { libFile.openZipSafe().use { zipFile -> for (import in imports) { val outputFile = targetDir.resolve(import.importFilePath) @@ -169,9 +176,9 @@ abstract class ImportLibraryFiles : BaseTask() { } } - private suspend fun usePatchLines(patches: Iterable, consumer: (String) -> Unit) = coroutineScope { + private suspend fun usePatchLines(patches: Iterable, dispatcher: CoroutineDispatcher, consumer: (String) -> Unit) = coroutineScope { for (patch in patches) { - launch(Dispatchers.IO) { + launch(dispatcher) { patch.useLines { lines -> lines.forEach { consumer(it) } } @@ -183,7 +190,8 @@ abstract class ImportLibraryFiles : BaseTask() { libraryImports: Path?, libFiles: List, index: Set, - patchFiles: Iterable + patchFiles: Iterable, + dispatcher: CoroutineDispatcher, ): Set { val result = hashSetOf() @@ -200,7 +208,7 @@ abstract class ImportLibraryFiles : BaseTask() { } // Scan patches for necessary imports - result += findNeededLibraryImports(patchFiles, index) + result += findNeededLibraryImports(patchFiles, index, dispatcher) return result } @@ -208,11 +216,12 @@ abstract class ImportLibraryFiles : BaseTask() { private suspend fun findNeededLibraryImports( patchFiles: Iterable, index: Set, + dispatcher: CoroutineDispatcher, ): Set { val knownImportMap = index.associateBy { it.importFilePath } val prefix = "+++ b/" val needed = ConcurrentHashMap.newKeySet() - usePatchLines(patchFiles) { line -> + usePatchLines(patchFiles, dispatcher) { line -> if (!line.startsWith(prefix)) { return@usePatchLines } diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/DownloadService.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/DownloadService.kt index 12aae51cd..6e6aabbc3 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/DownloadService.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/DownloadService.kt @@ -32,8 +32,6 @@ import java.time.format.DateTimeFormatter import java.util.Locale import java.util.concurrent.TimeUnit import kotlin.io.path.* -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope import org.apache.http.HttpHost import org.apache.http.HttpStatus import org.apache.http.client.config.CookieSpecs @@ -71,12 +69,6 @@ abstract class DownloadService : BuildService, AutoClose download(url, file, hash) } - suspend fun downloadAsync(source: Any, target: Any, hash: Hash? = null) = coroutineScope { - async { - download(source.convertToUrl(), target.convertToPath(), hash, false) - } - } - private fun download(source: URL, target: Path, hash: Hash?, retry: Boolean = false) { download(source, target) if (hash == null) { diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/DecompileJar.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/DecompileJar.kt index 3751b0cb5..33d469e2e 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/DecompileJar.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/DecompileJar.kt @@ -88,7 +88,7 @@ fun macheDecompileJar( ) { val out = outputJar.cleanFile() - val cfgFile = out.resolveSibling("${out.name}.cfg") + val cfgFile = workDir.resolve("${out.name}.cfg") val cfgText = buildString { for (file in minecraftClasspath) { append("-e=") @@ -98,7 +98,7 @@ fun macheDecompileJar( } cfgFile.writeText(cfgText) - val logs = out.resolveSibling("${out.name}.log") + val logs = workDir.resolve("${out.name}.log") val args = mutableListOf() diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/RemapJar.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/RemapJar.kt index 39dcb7e1d..ddeb9155f 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/RemapJar.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/tasks/mache/RemapJar.kt @@ -99,7 +99,7 @@ fun macheRemapJar( ) { val out = outputJar.cleanFile() - val logFile = out.resolveSibling("${out.name}.log") + val logFile = tempDir.resolve("${out.name}.log") val args = mutableListOf() diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt new file mode 100644 index 000000000..94da429f1 --- /dev/null +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt @@ -0,0 +1,143 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.util + +import io.papermc.paperweight.PaperweightException +import java.nio.file.Path +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock +import kotlin.io.path.* +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging + +private val openCurrentJvm: MutableMap = mutableMapOf() + +fun withLock( + lockFile: Path, + printInfoAfter: Long = 1000 * 60 * 5, // 5 minutes + timeoutMs: Long = 1000L * 60 * 60, // one hour + action: () -> R, +): R { + val logger = Logging.getLogger("paperweight lock file") + + var waitedMs = 0L + var firstFailedAcquire = true + while (true) { + val normalized = lockFile.normalize().absolute() + + val lock = synchronized(openCurrentJvm) { + openCurrentJvm.computeIfAbsent(normalized) { ReentrantLock() } + } + if (!lock.tryLock()) { + if (firstFailedAcquire) { + logger.lifecycle("Lock for '$lockFile' is currently held by another thread.") + logger.lifecycle("Waiting for lock to be released...") + firstFailedAcquire = false + } + val startWait = System.nanoTime() + val acquired = lock.tryLock(printInfoAfter, TimeUnit.MILLISECONDS) + if (!acquired) { + waitedMs += (System.nanoTime() - startWait) / 1_000_000 + if (waitedMs >= timeoutMs) { + throw PaperweightException("Have been waiting on lock for '$lockFile' for $waitedMs ms. Giving up as timeout is $timeoutMs ms.") + } + logger.lifecycle( + "Have been waiting on lock for '$lockFile' for ${waitedMs / 1000 / 60} minute(s).\n" + + "If this persists for an unreasonable length of time, kill this process, run './gradlew --stop', and then try again." + ) + } + } + val cont = synchronized(openCurrentJvm) { + if (openCurrentJvm[normalized] !== lock) { + lock.unlock() + true + } else { + false + } + } + if (cont) { + continue + } + + try { + acquireProcessLockWaiting(lockFile, logger, waitedMs, printInfoAfter, timeoutMs) + try { + return action() + } finally { + lockFile.deleteForcefully() + } + } finally { + synchronized(openCurrentJvm) { + lock.unlock() + openCurrentJvm.remove(normalized) + } + } + } +} + +// TODO: Open an actual exclusive lock using FileChannel +private fun acquireProcessLockWaiting( + lockFile: Path, + logger: Logger, + alreadyWaited: Long = 0, + printInfoAfter: Long, + timeoutMs: Long, +) { + val currentPid = ProcessHandle.current().pid() + + if (lockFile.exists()) { + val lockingProcessId = lockFile.readText().toLong() + if (lockingProcessId == currentPid) { + throw IllegalStateException("Lock file '$lockFile' is currently held by this process.") + } else { + logger.lifecycle("Lock file '$lockFile' is currently held by pid '$lockingProcessId'.") + } + + if (ProcessHandle.of(lockingProcessId).isEmpty) { + logger.lifecycle("Locking process does not exist, assuming abrupt termination and deleting lock file.") + lockFile.deleteIfExists() + } else { + logger.lifecycle("Waiting for lock to be released...") + var sleptMs: Long = alreadyWaited + while (lockFile.exists()) { + Thread.sleep(100) + sleptMs += 100 + if (sleptMs >= printInfoAfter && sleptMs % printInfoAfter == 0L) { + logger.lifecycle( + "Have been waiting on lock file '$lockFile' held by pid '$lockingProcessId' for ${sleptMs / 1000 / 60} minute(s).\n" + + "If this persists for an unreasonable length of time, kill this process, run './gradlew --stop' and then try again.\n" + + "If the problem persists, the lock file may need to be deleted manually." + ) + } + if (sleptMs >= timeoutMs) { + throw PaperweightException("Have been waiting on lock file '$lockFile' for $sleptMs ms. Giving up as timeout is $timeoutMs ms.") + } + } + } + } + + if (!lockFile.parent.exists()) { + lockFile.parent.createDirectories() + } + lockFile.writeText(currentPid.toString()) +} diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file.kt index e57b6d25c..ba17088b3 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file.kt @@ -22,7 +22,6 @@ package io.papermc.paperweight.util -import io.papermc.paperweight.PaperweightException import java.io.InputStream import java.io.OutputStream import java.net.URI @@ -48,7 +47,6 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileSystemLocation import org.gradle.api.file.FileSystemLocationProperty import org.gradle.api.file.RegularFileProperty -import org.gradle.api.logging.Logging import org.gradle.api.provider.Provider // utils for dealing with java.nio.file.Path and java.io.File @@ -68,7 +66,10 @@ fun Path.deleteForcefully() { deleteIfExists() } -fun Path.deleteRecursive(excludes: Iterable = emptyList()) { +fun Path.deleteRecursive( + excludes: Iterable = emptyList(), + preDelete: (Path) -> Unit = {} +) { if (!exists()) { return } @@ -76,6 +77,7 @@ fun Path.deleteRecursive(excludes: Iterable = emptyList()) { if (excludes.any { it.matches(this) }) { return } + preDelete(this) fixWindowsPermissionsForDeletion() deleteIfExists() return @@ -87,6 +89,7 @@ fun Path.deleteRecursive(excludes: Iterable = emptyList()) { fileList.forEach { f -> f.fixWindowsPermissionsForDeletion() } fileList.asReversed().forEach { f -> + preDelete(f) // Don't try to delete directories where the excludes glob has caused files to not get deleted inside it if (f.isRegularFile()) { f.deleteIfExists() @@ -172,51 +175,6 @@ fun Path.contentEquals(file: Path, bufferSizeBytes: Int = 8192): Boolean = file. fun Path.withDifferentExtension(ext: String): Path = resolveSibling("$nameWithoutExtension.$ext") -// Returns true if our process already owns the lock -fun acquireProcessLockWaiting( - lockFile: Path, - timeoutMs: Long = 1000L * 60 * 60 // one hour -): Boolean { - val logger = Logging.getLogger("paperweight lock file") - val currentPid = ProcessHandle.current().pid() - - if (lockFile.exists()) { - val lockingProcessId = lockFile.readText().toLong() - if (lockingProcessId == currentPid) { - return true - } - - logger.lifecycle("Lock file '$lockFile' is currently held by pid '$lockingProcessId'.") - if (ProcessHandle.of(lockingProcessId).isEmpty) { - logger.lifecycle("Locking process does not exist, assuming abrupt termination and deleting lock file.") - lockFile.deleteIfExists() - } else { - logger.lifecycle("Waiting for lock to be released...") - var sleptMs: Long = 0 - while (lockFile.exists()) { - Thread.sleep(100) - sleptMs += 100 - if (sleptMs >= 1000 * 60 && sleptMs % (1000 * 60) == 0L) { - logger.lifecycle( - "Have been waiting on lock file '$lockFile' held by pid '$lockingProcessId' for ${sleptMs / 1000 / 60} minute(s).\n" + - "If this persists for an unreasonable length of time, kill this process, run './gradlew --stop' and then try again.\n" + - "If the problem persists, the lock file may need to be deleted manually." - ) - } - if (sleptMs >= timeoutMs) { - throw PaperweightException("Have been waiting on lock file '$lockFile' for $sleptMs ms. Giving up as timeout is $timeoutMs ms.") - } - } - } - } - - if (!lockFile.parent.exists()) { - lockFile.parent.createDirectories() - } - lockFile.writeText(currentPid.toString()) - return false -} - fun relativeCopy(baseDir: Path, file: Path, outputDir: Path) { relativeCopyOrMove(baseDir, file, outputDir, false) } diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/jar-runner.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/jar-runner.kt index 77750ff38..355b1e8aa 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/jar-runner.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/jar-runner.kt @@ -23,6 +23,7 @@ package io.papermc.paperweight.util import io.papermc.paperweight.PaperweightException +import java.io.File import java.io.OutputStream import java.util.concurrent.TimeUnit import java.util.jar.JarFile @@ -31,15 +32,18 @@ import org.gradle.api.file.FileCollection import org.gradle.api.provider.Provider import org.gradle.jvm.toolchain.JavaLauncher +private val Iterable.asPath + get() = joinToString(File.pathSeparator) { it.absolutePath } + fun JavaLauncher.runJar( - classpath: FileCollection, + classpath: Iterable, workingDir: Any, logFile: Any?, jvmArgs: List = listOf(), vararg args: String ) { var mainClass: String? = null - for (file in classpath.files) { + for (file in classpath) { mainClass = JarFile(file).use { jarFile -> jarFile.manifest.mainAttributes.getValue("Main-Class") } ?: continue diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/utils.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/utils.kt index 5ba2dd7b8..4ae9fe9e1 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/utils.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/utils.kt @@ -45,11 +45,16 @@ import java.util.IdentityHashMap import java.util.Locale import java.util.Optional import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory import java.util.concurrent.ThreadLocalRandom +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import java.util.jar.Attributes import java.util.jar.Manifest import kotlin.io.path.* +import kotlinx.coroutines.ExecutorCoroutineDispatcher +import kotlinx.coroutines.asCoroutineDispatcher import org.cadixdev.lorenz.merge.MergeResult import org.gradle.api.Project import org.gradle.api.Task @@ -63,6 +68,7 @@ import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.invocation.Gradle import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.provider.Property @@ -315,14 +321,45 @@ fun String.hash(algorithm: HashingAlgorithm): ByteArray = algorithm.threadLocalD } fun InputStream.hash(algorithm: HashingAlgorithm, bufferSize: Int = 8192): ByteArray { + return listOf(InputStreamProvider.wrap(this)).hash(algorithm, bufferSize) +} + +interface InputStreamProvider { + fun use(op: (InputStream) -> T): T + + companion object { + fun file(path: Path): InputStreamProvider = object : InputStreamProvider { + override fun use(op: (InputStream) -> T): T { + return path.inputStream().use(op) + } + } + + fun dir(path: Path): List = path.walk() + .sortedBy { it.absolutePathString() } + .map { file -> file(file) } + .toList() + + fun wrap(input: InputStream): InputStreamProvider = object : InputStreamProvider { + override fun use(op: (InputStream) -> T): T { + return op(input) + } + } + } +} + +fun Iterable.hash(algorithm: HashingAlgorithm, bufferSize: Int = 8192): ByteArray { val digest = algorithm.threadLocalDigest val buffer = ByteArray(bufferSize) - while (true) { - val count = read(buffer) - if (count == -1) { - break + for (provider in this) { + provider.use { input -> + while (true) { + val count = input.read(buffer) + if (count == -1) { + break + } + digest.update(buffer, 0, count) + } } - digest.update(buffer, 0, count) } return digest.digest() } @@ -401,7 +438,7 @@ val mainCapabilityAttribute: Attribute = Attribute.of("io.papermc.paperw fun ConfigurationContainer.resolveMacheMeta() = getByName(MACHE_CONFIG).resolveMacheMeta() -fun FileCollection.resolveMacheMeta() = singleFile.toPath().openZip().use { zip -> +fun FileCollection.resolveMacheMeta() = singleFile.toPath().openZipSafe().use { zip -> gson.fromJson(zip.getPath("/mache.json").readText()) } @@ -439,3 +476,23 @@ fun Project.upstreamsDirectory(): Provider { val workDirFromProp = layout.dir(workDirProp.map { File(it) }) return workDirFromProp.orElse(rootProject.layout.cacheDir(UPSTREAMS)) } + +private val ioDispatcherCount = AtomicInteger(0) + +fun ioDispatcher(name: String): ExecutorCoroutineDispatcher = + Executors.newFixedThreadPool( + Runtime.getRuntime().availableProcessors(), + object : ThreadFactory { + val logger = Logging.getLogger("$name-ioDispatcher-${ioDispatcherCount.getAndIncrement()}") + val count = AtomicInteger(0) + + override fun newThread(r: Runnable): Thread { + val thr = Thread(r, "$name-ioDispatcher-${ioDispatcherCount.getAndIncrement()}-Thread-${count.getAndIncrement()}") + thr.setUncaughtExceptionHandler { thread, ex -> + logger.error("Uncaught exception in thread $thread", ex) + } + thr.isDaemon = true + return thr + } + } + ).asCoroutineDispatcher() diff --git a/paperweight-userdev/build.gradle.kts b/paperweight-userdev/build.gradle.kts index 2fd65463e..78d80db70 100644 --- a/paperweight-userdev/build.gradle.kts +++ b/paperweight-userdev/build.gradle.kts @@ -9,6 +9,7 @@ dependencies { implementation(variantOf(libs.diffpatch) { classifier("all") }) { isTransitive = false } + implementation(libs.coroutines) } gradlePlugin { diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt index 1eb53efc5..6428c8caa 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt @@ -32,23 +32,18 @@ import io.papermc.paperweight.userdev.internal.JunitExclusionRule import io.papermc.paperweight.userdev.internal.setup.SetupHandler import io.papermc.paperweight.userdev.internal.setup.UserdevSetup import io.papermc.paperweight.userdev.internal.setup.UserdevSetupTask -import io.papermc.paperweight.userdev.internal.setup.util.cleanSharedCaches -import io.papermc.paperweight.userdev.internal.setup.util.genSources -import io.papermc.paperweight.userdev.internal.setup.util.paperweightHash -import io.papermc.paperweight.userdev.internal.setup.util.sharedCaches +import io.papermc.paperweight.userdev.internal.util.cleanSharedCaches +import io.papermc.paperweight.userdev.internal.util.deleteUnusedAfter +import io.papermc.paperweight.userdev.internal.util.genSources +import io.papermc.paperweight.userdev.internal.util.sharedCaches import io.papermc.paperweight.util.* import io.papermc.paperweight.util.constants.* -import java.nio.file.Path import javax.inject.Inject import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.ModuleDependency -import org.gradle.api.artifacts.ProjectDependency -import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.artifacts.dsl.DependencyFactory -import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.attributes.Bundling import org.gradle.api.attributes.Category import org.gradle.api.attributes.LibraryElements @@ -81,16 +76,17 @@ abstract class PaperweightUser : Plugin { parameters.projectPath.set(target.projectDir) } - val cleanAll = target.tasks.register("cleanAllPaperweightUserdevCaches") { - group = GENERAL_TASK_GROUP - description = "Delete the project-local & all shared paperweight-userdev setup caches." - delete(target.layout.cache) - delete(sharedCacheRoot) - } val cleanCache by target.tasks.registering { group = GENERAL_TASK_GROUP description = "Delete the project-local paperweight-userdev setup cache." delete(target.layout.cache) + delete(target.rootProject.layout.cache.resolve("paperweight-userdev")) + } + val cleanAll = target.tasks.register("cleanAllPaperweightUserdevCaches") { + group = GENERAL_TASK_GROUP + description = "Delete the project-local & all shared paperweight-userdev setup caches." + delete(sharedCacheRoot) + dependsOn(cleanCache) } target.configurations.register(DEV_BUNDLE_CONFIG) { @@ -98,7 +94,7 @@ abstract class PaperweightUser : Plugin { } // must not be initialized until afterEvaluate, as it resolves the dev bundle - val userdevSetupProvider by lazy { createSetup(target, sharedCacheRoot.resolve(paperweightHash)) } + val userdevSetupProvider by lazy { createSetup(target) } val userdevSetup by lazy { userdevSetupProvider.get() } val userdev = target.extensions.create( @@ -126,7 +122,7 @@ abstract class PaperweightUser : Plugin { group = GENERAL_TASK_GROUP description = "Remap the compiled plugin jar to Spigot's obfuscated runtime names." - mappingsFile.pathProvider(target.provider { userdevSetup.reobfMappings }) + mappingsFile.set(setupTask.flatMap { it.reobfMappings }) remapClasspath.from(setupTask.flatMap { it.mappedServerJar }) fromNamespace.set(DEOBF_NAMESPACE) @@ -150,26 +146,6 @@ abstract class PaperweightUser : Plugin { } target.afterEvaluate { - // Manually check if cleanCache is a target, and skip setup. - // Gradle moved NameMatcher to internal packages in 7.1, so this solution isn't ideal, - // but it does work and allows using the cleanCache task without setting up the workspace first - val cleaningCache = gradle.startParameter.taskRequests - .any { req -> - req.args.any { arg -> - NameMatcher().find(arg, tasks.names) in setOf(cleanCache.name, cleanAll.name) - } - } - if (cleaningCache) { - return@afterEvaluate - } - - if (isIDEASync()) { - val startParameter = gradle.startParameter - val taskRequests = startParameter.taskRequests.toMutableList() - taskRequests.add(DefaultTaskExecutionRequest(listOf(setupTask.name))) - startParameter.setTaskRequests(taskRequests) - } - userdev.javaLauncher.convention(javaToolchainService.defaultJavaLauncher(this)) userdev.reobfArtifactConfiguration.get() @@ -187,6 +163,17 @@ abstract class PaperweightUser : Plugin { applyJunitExclusionRule() } + if (cleaningCache(cleanCache, cleanAll)) { + return@afterEvaluate + } + + if (isIDEASync()) { + val startParameter = gradle.startParameter + val taskRequests = startParameter.taskRequests.toMutableList() + taskRequests.add(DefaultTaskExecutionRequest(listOf(setupTask.name))) + startParameter.setTaskRequests(taskRequests) + } + // Print a friendly error message if the dev bundle is missing before we call anything else that will try and resolve it checkForDevBundle() @@ -199,10 +186,25 @@ abstract class PaperweightUser : Plugin { it.extendsFrom(configurations.getByName(MOJANG_MAPPED_SERVER_CONFIG)) } + // Clean v1 shared caches cleanSharedCaches(this, sharedCacheRoot) } } + private fun Project.cleaningCache(vararg cleanTasks: TaskProvider<*>): Boolean { + val cleanTaskNames = cleanTasks.map { it.name }.toSet() + + // Manually check if cleanCache is a target, and skip setup. + // Gradle moved NameMatcher to internal packages in 7.1, so this solution isn't ideal, + // but it does work and allows using the cleanCache task without setting up the workspace first + return gradle.startParameter.taskRequests + .any { req -> + req.args.any { arg -> + NameMatcher().find(arg, tasks.names) in cleanTaskNames + } + } + } + private fun Project.decorateJarManifests() { val op = Action { manifest { @@ -333,37 +335,26 @@ abstract class PaperweightUser : Plugin { private fun createContext(project: Project, setupTask: TaskProvider): SetupHandler.ConfigurationContext = SetupHandler.ConfigurationContext(project, dependencyFactory, setupTask) - private fun createSetup(target: Project, sharedCacheRoot: Path): Provider { + private fun createSetup(target: Project): Provider { val bundleConfig = target.configurations.named(DEV_BUNDLE_CONFIG) val devBundleZip = bundleConfig.map { it.singleFile }.convertToPath() val bundleHash = devBundleZip.sha256asHex() val cacheDir = if (!target.sharedCaches) { - target.layout.cache + target.rootProject.layout.cache.resolve("paperweight-userdev/v2/work") } else { - when (bundleConfig.get().dependencies.single()) { - is ProjectDependency -> target.layout.cache - - is ModuleDependency -> { - val resolved = - bundleConfig.get().incoming.resolutionResult.rootComponent.get().dependencies.single() as ResolvedDependencyResult - val resolvedId = resolved.selected.id as ModuleComponentIdentifier - sharedCacheRoot.resolve("module/${resolvedId.group}/${resolvedId.module}/${resolvedId.version}") - } - - else -> sharedCacheRoot.resolve("non-module/$bundleHash") - } + target.gradle.gradleUserHomeDir.toPath().resolve("caches/paperweight-userdev/v2/work") } val serviceName = "paperweight-userdev:setupService:$bundleHash" val ret = target.gradle.sharedServices .registerIfAbsent(serviceName, UserdevSetup::class) { - maxParallelUsages = 1 parameters { cache.set(cacheDir) bundleZip.set(devBundleZip) bundleZipHash.set(bundleHash) downloadService.set(target.download) genSources.set(target.genSources) + deleteUnusedAfter.set(deleteUnusedAfter(target)) } } buildEventsListenerRegistry.onTaskCompletion(ret) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt new file mode 100644 index 000000000..511ed4c30 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt @@ -0,0 +1,70 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.action + +import io.papermc.paperweight.util.* +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.* +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logging + +class CacheCleaner(private val work: Path) { + companion object { + private val logger = Logging.getLogger(CacheCleaner::class.java) + } + + fun cleanCache(deleteUnusedAfter: Long) { + if (!work.isDirectory()) { + return + } + + val start = System.nanoTime() + var deleted = 0 + var deletedSize = 0L + + work.listDirectoryEntries().forEach { + val lockFile = it.resolve("lock") + if (lockFile.exists()) { + return@forEach + } + val metadataFile = it.resolve("metadata.json") + if (!metadataFile.isRegularFile()) { + return@forEach + } + val since = System.currentTimeMillis() - metadataFile.getLastModifiedTime().toMillis() + if (since > deleteUnusedAfter) { + deleted++ + it.deleteRecursive { toDelete -> + if (toDelete.isRegularFile()) { + deletedSize += Files.size(toDelete) + } + } + } + } + + val took = System.nanoTime() - start + val level = if (deleted > 0) LogLevel.LIFECYCLE else LogLevel.INFO + logger.log(level, "paperweight-userdev: Cleaned $deleted files totaling ${deletedSize / 1024}KB in ${took / 1_000_000}ms") + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcher.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcher.kt new file mode 100644 index 000000000..91e3d4c80 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcher.kt @@ -0,0 +1,51 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.action + +import java.nio.file.Path + +interface WorkDispatcher { + fun outputFile(name: String): FileValue + + fun outputDir(name: String): DirectoryValue + + fun provided(value: Value<*>) + + fun provided(vararg values: Value<*>) = values.forEach { provided(it) } + + fun register(name: String, workUnit: T): T + + fun registered(name: String): T + + fun overrideTerminalInputHash(hash: String) + + fun dispatch(vararg targets: Value<*>, progressEventListener: (String) -> Unit = {}) + + interface Action { + fun execute() + } + + companion object { + fun create(work: Path): WorkDispatcher = WorkDispatcherImpl(work) + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt new file mode 100644 index 000000000..a4bdbc82b --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt @@ -0,0 +1,131 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.action + +import io.papermc.paperweight.PaperweightException +import io.papermc.paperweight.userdev.internal.util.formatNs +import java.nio.file.Path +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.isSuperclassOf +import kotlin.reflect.jvm.isAccessible +import kotlin.system.measureNanoTime +import org.gradle.api.logging.Logging + +class WorkDispatcherImpl(private val work: Path) : WorkDispatcher { + companion object { + private val logger = Logging.getLogger(WorkDispatcherImpl::class.java) + } + + private val provided = mutableSetOf>() + private val registrations = mutableSetOf() + private var terminalInputHash: String? = null + + data class Registration( + val name: String, + val action: WorkDispatcher.Action, + val inputs: List>, + val outputs: List>, + ) + + override fun outputFile(name: String): FileValue = LazyFileValue(name) + + override fun outputDir(name: String): DirectoryValue = LazyDirectoryValue(name) + + override fun provided(value: Value<*>) { + if (registrations.any { it.outputs.contains(value) }) { + throw PaperweightException("Value $value is an output of a registered work unit") + } + if (!provided.add(value)) { + throw PaperweightException("Value $value has already been provided") + } + } + + override fun register(name: String, workUnit: T): T { + if (registrations.any { it.name == name }) { + throw PaperweightException("Work unit with name $name has already been registered") + } + val data = Registration( + name, + workUnit, + workUnit::class.collectAnnotatedDeclaredProperties().extractValues(workUnit), + workUnit::class.collectAnnotatedDeclaredProperties().extractValues(workUnit), + ) + if (data.outputs.any { it in provided }) { + throw PaperweightException("Output of work unit $name has already been provided") + } + registrations.add(data) + return workUnit + } + + @Suppress("UNCHECKED_CAST") + override fun registered(name: String): T { + return registrations.firstOrNull { it.name == name }?.action as? T + ?: throw PaperweightException("No work unit registered with name $name") + } + + override fun overrideTerminalInputHash(hash: String) { + terminalInputHash = hash + } + + override fun dispatch(vararg targets: Value<*>, progressEventListener: (String) -> Unit) { + val targetSet = targets.toSet() + val start = System.nanoTime() + val graph = WorkGraph( + provided.toSet(), + registrations.toSet(), + terminalInputHash, + targetSet, + ) + val took = System.nanoTime() - start + logger.info("Graph building for $targetSet took ${formatNs(took)}") + val executedIn = measureNanoTime { + graph.execute(work, progressEventListener) + } + logger.info("Execution of $targetSet took ${formatNs(executedIn)}") + } + + private fun List>.extractValues(action: WorkDispatcher.Action): List> { + return mapNotNull { + if (!Value::class.isSuperclassOf(it.returnType.classifier as KClass<*>)) { + throw PaperweightException("Input/output property $it does not return a Value") + } + it.get(action) as Value<*>? + } + } + + private inline fun KClass<*>.collectAnnotatedDeclaredProperties() = + collectDeclaredProperties { + it.annotations.any { a -> a is A } + } + + @Suppress("unchecked_cast") + private fun KClass<*>.collectDeclaredProperties( + filter: (KProperty1<*, *>) -> Boolean + ): List> = + declaredMemberProperties.filter(filter).map { + it.isAccessible = true + it as KProperty1 + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt new file mode 100644 index 000000000..5266fa5cb --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt @@ -0,0 +1,214 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.action + +import io.papermc.paperweight.PaperweightException +import io.papermc.paperweight.userdev.internal.util.formatNs +import io.papermc.paperweight.util.* +import java.nio.file.Path +import java.util.IdentityHashMap +import kotlin.io.path.* +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging + +class WorkGraph( + private val provided: Set>, + private val registrations: Set, + private val terminalInputHash: String?, + private val requested: Set>, +) { + companion object { + private val logger = Logging.getLogger(WorkGraph::class.java) + } + + class Node( + val registration: WorkDispatcherImpl.Registration, + val dependencies: List, + ) + + private val roots: List = buildGraph(requested.toList()) + + init { + logger.printGraph(LogLevel.INFO, roots) + } + + private fun Logger.printGraph(level: LogLevel, nodes: List, indent: String = "") { + for (node in nodes) { + logger.log(level, "$indent${node.registration.name}") + printGraph(level, node.dependencies, "$indent\t") + } + } + + private fun buildGraph( + requested: List>, + nodeCache: MutableMap = IdentityHashMap(), + ): List { + val nodes = mutableListOf() + for (req in requested) { + if (provided.contains(req)) { + continue + } + val producers = registrations.filter { it.outputs.contains(req) } + if (producers.isEmpty()) { + throw PaperweightException("No producer or provider for value $req") + } + if (producers.size > 1) { + throw PaperweightException("Multiple producers for value $req") + } + val producer = producers.single() + val node = nodeCache[producer] ?: Node(producer, buildGraph(producer.inputs, nodeCache)).also { + // This is only used as debug information + it.registration.outputs.forEach { output -> + when (output) { + is LazyFileValue -> output.owner = it.registration.name + is LazyDirectoryValue -> output.owner = it.registration.name + } + } + } + nodeCache[producer] = node + nodes += node + } + return nodes + } + + fun execute(work: Path, progressEventListener: (String) -> Unit = {}) { + val visited = mutableSetOf() + val hashCache = mutableMapOf, String>() + for (node in roots) { + executeNode(work, node, visited, hashCache, progressEventListener) + } + } + + data class Metadata( + val outputHashes: List, + val lastUsed: Long = System.currentTimeMillis(), + ) { + fun updateLastUsed() = copy(lastUsed = System.currentTimeMillis()) + + fun writeTo(file: Path) { + file.createParentDirectories().bufferedWriter().use { gson.toJson(this, it) } + } + } + + private fun executeNode( + work: Path, + node: Node, + visited: MutableSet, + hashCache: MutableMap, String>, + progressEventListener: (String) -> Unit, + retry: Boolean = false, + ) { + val root = node.registration.outputs.any { it in requested } + + if (!retry && !visited.add(node)) { + return + } + + val earlyUpToDateCheck = root && !retry && terminalInputHash != null + + if (!earlyUpToDateCheck) { + for (dep in node.dependencies) { + executeNode(work, dep, visited, hashCache, progressEventListener) + } + } + + progressEventListener(node.registration.name) + + val start = System.nanoTime() + val inputHash = if (terminalInputHash != null && root) { + terminalInputHash + } else { + val inputHashes = node.registration.inputs.hash(hashCache) + inputHashes.map { InputStreamProvider.wrap(it.byteInputStream()) } + .hash(HashingAlgorithm.SHA256) + .asHexString() + } + + realizeOutputPaths(node, work, inputHash) + + val lockFile = work.resolve("${node.registration.name}_$inputHash/lock") + + val hashFile = work.resolve("${node.registration.name}_$inputHash/metadata.json") + val upToDate = withLock(lockFile) { + if (hashFile.exists()) { + val metadata = hashFile.bufferedReader().use { gson.fromJson(it, Metadata::class.java) } + if (node.registration.outputs.hash(hashCache) == metadata.outputHashes) { + logger.lifecycle("Skipping ${node.registration.name} (up-to-date)") + metadata.updateLastUsed().writeTo(hashFile) + val took = System.nanoTime() - start + logger.info("Up-to-date check for ${node.registration.name} took ${formatNs(took)} (up-to-date)") + return@withLock true + } else { + node.registration.outputs.forEach { hashCache.remove(it) } + val took = System.nanoTime() - start + logger.info("Up-to-date check for ${node.registration.name} took ${formatNs(took)} (out-of-date)") + } + } + + if (!earlyUpToDateCheck) { + logger.lifecycle("Executing ${node.registration.name}...") + val startExec = System.nanoTime() + + try { + node.registration.action.execute() + } catch (e: Exception) { + throw PaperweightException("Exception executing ${node.registration.name}", e) + } + + val metadata = Metadata(node.registration.outputs.hash(hashCache)) + metadata.writeTo(hashFile) + + val tookExec = System.nanoTime() - startExec + logger.lifecycle("Finished ${node.registration.name} in ${formatNs(tookExec)}") + } + + return@withLock false + } + if (upToDate) { + return + } + + if (earlyUpToDateCheck) { + executeNode(work, node, visited, hashCache, progressEventListener, true) + } + } + + private fun List>.hash(cache: MutableMap, String>): List { + return map { + cache.computeIfAbsent(it) { v -> + v.bytes().hash(HashingAlgorithm.SHA256).asHexString() + } + } + } + + private fun realizeOutputPaths(node: Node, work: Path, inputHash: String) { + for (out in node.registration.outputs) { + when (out) { + is LazyFileValue -> out.path = work.resolve("${node.registration.name}_$inputHash/${out.name}") + is LazyDirectoryValue -> out.path = work.resolve("${node.registration.name}_$inputHash/${out.name}") + else -> throw PaperweightException("Unsupported output type ${out::class.java.name}") + } + } + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt new file mode 100644 index 000000000..68e5e5246 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt @@ -0,0 +1,165 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.action + +import io.papermc.paperweight.util.* +import java.io.InputStream +import java.nio.file.Path +import kotlin.io.path.* +import org.gradle.api.file.FileCollection +import org.gradle.jvm.toolchain.JavaLauncher + +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class Input + +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.RUNTIME) +annotation class Output + +interface Value { + fun get(): V + + fun bytes(): List +} + +fun value(value: V, bytes: (V) -> List): Value = object : Value { + override fun get(): V = value + + override fun bytes(): List = bytes(get()) + + override fun toString(): String = "value('$value')" +} + +class FileCollectionValue(private val files: FileCollection) : Value { + override fun get(): FileCollection = files + + override fun bytes(): List = files.files + .sortedBy { it.absolutePath } + .flatMap { + if (it.isDirectory) { + InputStreamProvider.dir(it.toPath()) + } else { + listOf(InputStreamProvider.file(it.toPath())) + } + } + + override fun toString(): String = "FileCollectionValue('$files')" +} + +class StringValue(private val value: String) : Value { + override fun get(): String = value + + override fun bytes(): List = listOf( + object : InputStreamProvider { + override fun use(op: (InputStream) -> T): T { + return value.byteInputStream().use(op) + } + } + ) + + override fun toString(): String = "StringValue('$value')" +} + +interface FileValue : Value { + override fun bytes(): List { + if (!get().exists()) { + return listOf() + } + return listOf(InputStreamProvider.file(get())) + } +} + +interface DirectoryValue : Value { + override fun bytes(): List { + if (!get().exists()) { + return listOf() + } + return InputStreamProvider.dir(get()) + } +} + +fun fileValue(path: Path): FileValue = FileValueImpl(path) +fun directoryValue(path: Path): DirectoryValue = DirectoryValueImpl(path) + +class ListValue(private val values: List>) : Value> { + override fun get(): List = values.map { it.get() } + + override fun bytes(): List = values.flatMap { it.bytes() } + + override fun toString(): String = "ListValue([${values.joinToString()}])" +} + +fun stringListValue(values: List): ListValue { + return ListValue(values.map { StringValue(it) }) +} + +fun fileListValue(files: List): ListValue { + return ListValue(files.map { fileValue(it) }) +} + +class FileValueImpl(private val path: Path) : FileValue { + override fun get(): Path = path + + override fun toString(): String = "FileValue('$path')" +} + +class DirectoryValueImpl(private val path: Path) : DirectoryValue { + override fun get(): Path = path + + override fun toString(): String = "DirectoryValue('$path')" +} + +class LazyFileValue(val name: String) : FileValue { + var path: Path? = null + var owner: String? = null + + override fun get(): Path = requireNotNull(path) { "Path is not yet populated" } + + override fun toString(): String = "LazyFileValue(name='$name', owner='$owner')" +} + +class LazyDirectoryValue(val name: String) : DirectoryValue { + var path: Path? = null + var owner: String? = null + + override fun get(): Path = requireNotNull(path) { "Path is not yet populated" } + + override fun toString(): String = "LazyDirectoryValue(name='$name', owner='$owner')" +} + +fun javaLauncherValue(javaLauncher: JavaLauncher): Value = object : Value { + override fun get(): JavaLauncher = javaLauncher + + override fun bytes(): List { + val jdkMetadata = listOf( + javaLauncher.metadata.javaRuntimeVersion, + javaLauncher.metadata.jvmVersion, + javaLauncher.metadata.vendor, + javaLauncher.metadata.languageVersion.asInt().toString(), + ).joinToString("\n") + return listOf(InputStreamProvider.wrap(jdkMetadata.byteInputStream())) + } + + override fun toString(): String = "javaLauncherValue('$javaLauncher')" +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/ExtractDevBundle.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt similarity index 63% rename from paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/ExtractDevBundle.kt rename to paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt index bc0d0daf1..afb1779db 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/ExtractDevBundle.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt @@ -38,45 +38,23 @@ private val supported = mapOf( 6 to GenerateDevBundle.DevBundleConfig::class // Post-repo-restructure 1.21.4+ ) -data class ExtractedBundle( - val changed: Boolean, - val config: C, - val dataVersion: Int, - val dir: Path, -) { - constructor(bundleChanged: Boolean, pair: Pair, dir: Path) : - this(bundleChanged, pair.first, pair.second, dir) -} - -fun extractDevBundle( - destinationDirectory: Path, - devBundle: Path, - newDevBundleHash: String -): ExtractedBundle { - val hashFile = destinationDirectory.resolve("current.sha256") - - if (destinationDirectory.exists()) { - val currentDevBundleHash = if (hashFile.isRegularFile()) hashFile.readText(Charsets.UTF_8) else "" - - if (currentDevBundleHash.isNotBlank() && newDevBundleHash == currentDevBundleHash) { - return ExtractedBundle(false, readDevBundle(destinationDirectory), destinationDirectory) +fun readBundleInfo(bundleZip: Path): BundleInfo { + bundleZip.openZipSafe().use { fs -> + readDevBundle(fs.getPath("/")).let { (config, _) -> + return BundleInfo(config, bundleZip) } - destinationDirectory.deleteRecursive() } - destinationDirectory.createDirectories() - - hashFile.writeText(newDevBundleHash, Charsets.UTF_8) - devBundle.openZip().use { fs -> - fs.getPath("/").copyRecursivelyTo(destinationDirectory) - } - - return ExtractedBundle(true, readDevBundle(destinationDirectory), destinationDirectory) } +data class BundleInfo( + val config: C, + val zip: Path, +) + private fun readDevBundle( - extractedDevBundlePath: Path + devBundleRoot: Path ): Pair { - val dataVersion = extractedDevBundlePath.resolve("data-version.txt").readText().trim().toInt() + val dataVersion = devBundleRoot.resolve("data-version.txt").readText().trim().toInt() if (dataVersion !in supported) { throw PaperweightException( "The paperweight development bundle you are attempting to use is of data version '$dataVersion', but" + @@ -85,7 +63,7 @@ private fun readDevBundle( } val configClass = supported[dataVersion] ?: throw PaperweightException("Could not find config class for version $dataVersion?") - val configFile = extractedDevBundlePath.resolve("config.json") + val configFile = devBundleRoot.resolve("config.json") val config: Any = configFile.bufferedReader(Charsets.UTF_8).use { reader -> gson.fromJson(reader, configClass.java) } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt index 44f6e6414..b38933ceb 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt @@ -38,8 +38,9 @@ import org.gradle.api.file.FileCollection import org.gradle.api.file.ProjectLayout import org.gradle.api.logging.Logger import org.gradle.api.tasks.TaskProvider +import org.gradle.internal.logging.progress.ProgressLogger +import org.gradle.internal.logging.progress.ProgressLoggerFactory import org.gradle.jvm.toolchain.JavaLauncher -import org.gradle.kotlin.dsl.* import org.gradle.workers.WorkerExecutor interface SetupHandler { @@ -47,16 +48,13 @@ interface SetupHandler { fun populateRuntimeConfiguration(context: ConfigurationContext, dependencySet: DependencySet) - fun combinedOrClassesJar(context: ExecutionContext): Path + fun generateCombinedOrClassesJar(context: ExecutionContext, output: Path, legacyOutput: Path?) + + fun extractReobfMappings(output: Path) fun afterEvaluate(project: Project) { - project.tasks.withType(UserdevSetupTask::class).configureEach { - devBundleCoordinates.set(determineArtifactCoordinates(project.configurations.getByName(DEV_BUNDLE_CONFIG)).single()) - } } - val reobfMappings: Path - val minecraftVersion: String val pluginRemapArgs: List @@ -95,6 +93,7 @@ interface SetupHandler { val javaLauncher: JavaLauncher, val layout: ProjectLayout, val logger: Logger, + val progressLoggerFactory: ProgressLoggerFactory, val decompilerConfig: FileCollection, val paramMappingsConfig: FileCollection, @@ -105,30 +104,44 @@ interface SetupHandler { val macheParamMappingsConfig: FileCollection, val macheConstantsConfig: FileCollection, val macheCodebookConfig: FileCollection, - ) + ) { + fun withProgressLogger( + name: String = "execute", + description: String = "paperweight userdev setup", + action: (ProgressLogger) -> Unit + ) { + val progressLogger = progressLoggerFactory.newOperation(name) + progressLogger.start(name, description) + try { + action(progressLogger) + } finally { + progressLogger.completed() + } + } + } companion object { @Suppress("unchecked_cast") fun create( parameters: UserdevSetup.Parameters, - extractedBundle: ExtractedBundle - ): SetupHandler = when (extractedBundle.config) { + bundleInfo: BundleInfo + ): SetupHandler = when (bundleInfo.config) { is GenerateDevBundle.DevBundleConfig -> SetupHandlerImpl( parameters, - extractedBundle as ExtractedBundle, + bundleInfo as BundleInfo, ) is DevBundleV5.Config -> SetupHandlerImplV5( parameters, - extractedBundle as ExtractedBundle + bundleInfo as BundleInfo ) is DevBundleV2.Config -> SetupHandlerImplV2( parameters, - extractedBundle as ExtractedBundle + bundleInfo as BundleInfo ) - else -> throw PaperweightException("Unknown dev bundle config type: ${extractedBundle.config::class.java.typeName}") + else -> throw PaperweightException("Unknown dev bundle config type: ${bundleInfo.config::class.java.typeName}") } } } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt index ea31ecda8..d367b5d5d 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt @@ -23,12 +23,23 @@ package io.papermc.paperweight.userdev.internal.setup import io.papermc.paperweight.tasks.* -import io.papermc.paperweight.userdev.internal.setup.step.* -import io.papermc.paperweight.userdev.internal.setup.util.lockSetup +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.StringValue +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.action.fileValue +import io.papermc.paperweight.userdev.internal.action.javaLauncherValue +import io.papermc.paperweight.userdev.internal.action.stringListValue +import io.papermc.paperweight.userdev.internal.setup.action.ApplyDevBundlePatchesAction +import io.papermc.paperweight.userdev.internal.setup.action.ExtractFromBundlerAction +import io.papermc.paperweight.userdev.internal.setup.action.RemapMinecraftMacheAction +import io.papermc.paperweight.userdev.internal.setup.action.RunPaperclipAction +import io.papermc.paperweight.userdev.internal.setup.action.SetupMacheSourcesAction +import io.papermc.paperweight.userdev.internal.setup.action.VanillaServerDownloads import io.papermc.paperweight.util.* import io.papermc.paperweight.util.constants.* import io.papermc.paperweight.util.data.mache.* import java.nio.file.Path +import kotlin.io.path.* import org.gradle.api.Project import org.gradle.api.artifacts.DependencySet import org.gradle.api.file.FileCollection @@ -36,91 +47,109 @@ import org.gradle.kotlin.dsl.* class SetupHandlerImpl( private val parameters: UserdevSetup.Parameters, - private val bundle: ExtractedBundle, - private val cache: Path = parameters.cache.path, + private val bundle: BundleInfo, ) : SetupHandler { private var macheMeta: MacheMeta? = null - private val vanillaSteps by lazy { - VanillaSteps( - bundle.config.minecraftVersion, - cache, - parameters.downloadService.get(), - bundle.changed, - ) - } - private val vanillaServerJar: Path = cache.resolve(paperSetupOutput("vanillaServerJar", "jar")) - private val minecraftLibraryJars = cache.resolve(MINECRAFT_JARS_PATH) - private val mappedServerJar: Path = cache.resolve(paperSetupOutput("remapServerJar", "jar")) - private val baseSources: Path = cache.resolve(paperSetupOutput("baseSources", "jar")) - private val patchedSourcesJar: Path = cache.resolve(paperSetupOutput("patchedSources", "jar")) - private val mojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("applyMojangMappedPaperclipPatch", "jar")) - - private fun minecraftLibraryJars(): List = minecraftLibraryJars.filesMatchingRecursive("*.jar") - - private fun generateSources(context: SetupHandler.ExecutionContext) { - setupMacheMeta(context.macheConfig) // If the config cache is reused then the mache config may not be populated - vanillaSteps.downloadVanillaServerJar() - vanillaSteps.downloadServerMappings() - applyMojangMappedPaperclipPatch(context) - - val extractStep = createExtractFromBundlerStep() - - val remapStep = RemapMinecraftMache.create( - context, - macheMeta().remapperArgs, - vanillaServerJar, - ::minecraftLibraryJars, - vanillaSteps.serverMappings, - mappedServerJar, - cache, + + private fun createDispatcher(context: SetupHandler.ExecutionContext): WorkDispatcher { + val dispatcher = WorkDispatcher.create(parameters.cache.path) + dispatcher.overrideTerminalInputHash(parameters.bundleZipHash.get()) + + val javaLauncher = javaLauncherValue(context.javaLauncher) + val mcVer = StringValue(bundle.config.minecraftVersion) + val bundleZip = fileValue(bundle.zip) + dispatcher.provided( + javaLauncher, + mcVer, + bundleZip, ) - val macheSourcesStep = MinecraftSourcesMache.create( - context, - mappedServerJar, - baseSources, - cache, - ::minecraftLibraryJars, - macheMeta().decompilerArgs, + val vanillaDownloads = dispatcher.register( + "vanillaServerDownloads", + VanillaServerDownloads( + mcVer, + dispatcher.outputFile("vanillaServer.jar"), + dispatcher.outputFile("mojangServerMappings.txt"), + parameters.downloadService.get(), + ) ) - val applyDevBundlePatchesStep = ApplyDevBundlePatches( - baseSources, - bundle.dir.resolve(bundle.config.patchDir), - patchedSourcesJar, - mojangMappedPaperJar + val applyPaperclip = dispatcher.register( + "applyPaperclipPatch", + RunPaperclipAction( + javaLauncher, + bundleZip, + StringValue(bundle.config.mojangMappedPaperclipFile), + dispatcher.outputFile("output.jar"), + vanillaDownloads.serverJar, + mcVer, + ) + ) + dispatcher.provided(applyPaperclip.paperclipPath) + + val extract = dispatcher.register( + "extractFromBundler", + ExtractFromBundlerAction( + vanillaDownloads.serverJar, + dispatcher.outputFile("vanillaServer.jar"), + dispatcher.outputDir("minecraftLibraries"), + ) ) - StepExecutor.executeSteps( - bundle.changed, - context, - extractStep, - remapStep, - macheSourcesStep, - applyDevBundlePatchesStep, + val remap = dispatcher.register( + "remapMinecraft", + RemapMinecraftMacheAction( + javaLauncher, + stringListValue(macheMeta().remapperArgs), + extract.vanillaServerJar, + extract.minecraftLibraryJars, + vanillaDownloads.serverMappings, + FileCollectionValue(context.macheParamMappingsConfig), + FileCollectionValue(context.macheConstantsConfig), + FileCollectionValue(context.macheCodebookConfig), + FileCollectionValue(context.macheRemapperConfig), + dispatcher.outputFile("output.jar"), + ) + ) + dispatcher.provided( + remap.minecraftRemapArgs, + remap.paramMappings, + remap.constants, + remap.codebook, + remap.remapper, ) - } - // This can be called when a user queries the server jar provider in - // PaperweightUserExtension, possibly by a task running in a separate - // thread to dependency resolution. - @Synchronized - private fun applyMojangMappedPaperclipPatch(context: SetupHandler.ExecutionContext) { - if (setupCompleted) { - return - } + val macheSources = dispatcher.register( + "setupMacheSources", + SetupMacheSourcesAction( + javaLauncher, + remap.outputJar, + dispatcher.outputFile("output.zip"), + extract.minecraftLibraryJars, + stringListValue(macheMeta().decompilerArgs), + FileCollectionValue(context.macheDecompilerConfig), + FileCollectionValue(context.macheConfig), + ) + ) + dispatcher.provided( + macheSources.decompileArgs, + macheSources.decompiler, + macheSources.mache, + ) - lockSetup(cache, true) { - StepExecutor.executeStep( - context, - RunPaperclip( - bundle.dir.resolve(bundle.config.mojangMappedPaperclipFile), - mojangMappedPaperJar, - vanillaSteps.mojangJar, - minecraftVersion, - ) + val applyPatches = dispatcher.register( + "applyDevBundlePatches", + ApplyDevBundlePatchesAction( + macheSources.outputJar, + bundleZip, + StringValue(bundle.config.patchDir), + dispatcher.outputFile("output.jar"), + applyPaperclip.outputJar, ) - } + ) + dispatcher.provided(applyPatches.patchesPath) + + return dispatcher } override fun populateCompileConfiguration(context: SetupHandler.ConfigurationContext, dependencySet: DependencySet) { @@ -132,33 +161,38 @@ class SetupHandlerImpl( populateCompileConfiguration(context, dependencySet) } - private var setupCompleted = false + @Volatile + private var completedOutput: Path? = null @Synchronized - override fun combinedOrClassesJar(context: SetupHandler.ExecutionContext): Path { - if (setupCompleted) { - return if (parameters.genSources.get()) { - patchedSourcesJar - } else { - mojangMappedPaperJar - } + override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + if (completedOutput != null) { + requireNotNull(completedOutput).copyTo(output, true) + return } - val ret = lockSetup(cache) { - if (parameters.genSources.get()) { - generateSources(context) - patchedSourcesJar - } else { - vanillaSteps.downloadVanillaServerJar() - StepExecutor.executeStep(context, createExtractFromBundlerStep()) - applyMojangMappedPaperclipPatch(context) - mojangMappedPaperJar + // If the config cache is reused then the mache config may not be populated + setupMacheMeta(context.macheConfig) + + val dispatcher = createDispatcher(context) + val request = if (parameters.genSources.get()) { + dispatcher.registered("applyDevBundlePatches").outputJar + } else { + dispatcher.registered("applyPaperclipPatch").outputJar + } + context.withProgressLogger { progressLogger -> + dispatcher.dispatch(request) { + progressLogger.progress(it) } } + request.get().copyTo(output, true) + completedOutput = request.get() + } - setupCompleted = true - - return ret + override fun extractReobfMappings(output: Path) { + bundle.zip.openZipSafe().use { fs -> + fs.getPath(bundle.config.reobfMappingsFile).copyTo(output, true) + } } private fun macheMeta(): MacheMeta = requireNotNull(macheMeta) { "Mache meta is not setup yet" } @@ -194,12 +228,6 @@ class SetupHandlerImpl( } project.tasks.withType(UserdevSetupTask::class).configureEach { - if (parameters.genSources.get()) { - mappedServerJar.set(patchedSourcesJar) - } else { - mappedServerJar.set(mojangMappedPaperJar) - } - macheCodebookConfig.from(macheCodebook) macheRemapperConfig.from(macheRemapper) macheDecompilerConfig.from(macheDecompiler) @@ -211,9 +239,6 @@ class SetupHandlerImpl( macheMeta().addRepositories(project) } - override val reobfMappings: Path - get() = bundle.dir.resolve(bundle.config.reobfMappingsFile) - override val minecraftVersion: String get() = bundle.config.minecraftVersion @@ -234,12 +259,4 @@ class SetupHandlerImpl( override val libraryRepositories: List get() = bundle.config.libraryRepositories - - private fun createExtractFromBundlerStep(): ExtractFromBundlerStep = ExtractFromBundlerStep( - cache, - vanillaSteps, - vanillaServerJar, - minecraftLibraryJars, - ::minecraftLibraryJars - ) } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt index d45df4a5d..6d444a83b 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt @@ -23,16 +23,12 @@ package io.papermc.paperweight.userdev.internal.setup import io.papermc.paperweight.DownloadService -import io.papermc.paperweight.userdev.internal.setup.util.* +import io.papermc.paperweight.userdev.internal.action.CacheCleaner import io.papermc.paperweight.util.* -import io.papermc.paperweight.util.constants.* import java.nio.file.Path -import kotlin.io.path.* import org.gradle.api.Project import org.gradle.api.artifacts.DependencySet import org.gradle.api.file.RegularFileProperty -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging import org.gradle.api.provider.Property import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters @@ -41,32 +37,21 @@ import org.gradle.tooling.events.OperationCompletionListener abstract class UserdevSetup : BuildService, SetupHandler, AutoCloseable, OperationCompletionListener { - companion object { - val LOGGER: Logger = Logging.getLogger(UserdevSetup::class.java) - } - interface Parameters : BuildServiceParameters { val bundleZip: RegularFileProperty val bundleZipHash: Property val cache: RegularFileProperty val downloadService: Property val genSources: Property + val deleteUnusedAfter: Property } - private val extractDevBundle: ExtractedBundle = lockSetup(parameters.cache.path) { - val extract = extractDevBundle( - parameters.cache.path.resolve(paperSetupOutput("extractDevBundle", "dir")), - parameters.bundleZip.path, - parameters.bundleZipHash.get() - ) - lastUsedFile(parameters.cache.path).writeText(System.currentTimeMillis().toString()) - extract - } + private val bundleInfo: BundleInfo = readBundleInfo(parameters.bundleZip.path) private val setup = createSetup() private fun createSetup(): SetupHandler = - SetupHandler.create(parameters, extractDevBundle) + SetupHandler.create(parameters, bundleInfo) override fun onFinish(event: FinishEvent?) { // no-op, a workaround to keep the service alive for the entire build @@ -74,7 +59,8 @@ abstract class UserdevSetup : BuildService, SetupHandle } override fun close() { - // see comments in onFinish + CacheCleaner(parameters.cache.path) + .cleanCache(parameters.deleteUnusedAfter.get()) } // begin delegate to setup @@ -86,17 +72,18 @@ abstract class UserdevSetup : BuildService, SetupHandle setup.populateRuntimeConfiguration(context, dependencySet) } - override fun combinedOrClassesJar(context: SetupHandler.ExecutionContext): Path { - return setup.combinedOrClassesJar(context) + override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + setup.generateCombinedOrClassesJar(context, output, legacyOutput) + } + + override fun extractReobfMappings(output: Path) { + setup.extractReobfMappings(output) } override fun afterEvaluate(project: Project) { setup.afterEvaluate(project) } - override val reobfMappings: Path - get() = setup.reobfMappings - override val minecraftVersion: String get() = setup.minecraftVersion diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt index 3b0f71402..de26c4c4a 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt @@ -23,17 +23,22 @@ package io.papermc.paperweight.userdev.internal.setup import io.papermc.paperweight.tasks.* +import io.papermc.paperweight.userdev.internal.util.formatNs +import io.papermc.paperweight.util.* import io.papermc.paperweight.util.constants.* import javax.inject.Inject +import kotlin.io.path.* +import kotlin.system.measureNanoTime import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.services.ServiceReference import org.gradle.api.tasks.CompileClasspath -import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.progress.ProgressLoggerFactory import org.gradle.workers.WorkerExecutor abstract class UserdevSetupTask : JavaLauncherTask() { @@ -43,22 +48,19 @@ abstract class UserdevSetupTask : JavaLauncherTask() { @get:Inject abstract val workerExecutor: WorkerExecutor - @get:Input - abstract val devBundleCoordinates: Property - - @get:CompileClasspath + @get:InputFiles abstract val devBundle: ConfigurableFileCollection @get:CompileClasspath abstract val decompilerConfig: ConfigurableFileCollection - @get:CompileClasspath + @get:InputFiles abstract val paramMappingsConfig: ConfigurableFileCollection @get:CompileClasspath abstract val macheDecompilerConfig: ConfigurableFileCollection - @get:CompileClasspath + @get:InputFiles abstract val macheConfig: ConfigurableFileCollection @get:CompileClasspath @@ -67,10 +69,10 @@ abstract class UserdevSetupTask : JavaLauncherTask() { @get:CompileClasspath abstract val macheRemapperConfig: ConfigurableFileCollection - @get:CompileClasspath + @get:InputFiles abstract val macheParamMappingsConfig: ConfigurableFileCollection - @get:CompileClasspath + @get:InputFiles abstract val macheConstantsConfig: ConfigurableFileCollection @get:CompileClasspath @@ -83,8 +85,16 @@ abstract class UserdevSetupTask : JavaLauncherTask() { @get:Optional abstract val legacyPaperclipResult: RegularFileProperty + @get:OutputFile + abstract val reobfMappings: RegularFileProperty + + @get:Inject + abstract val progressLoggerFactory: ProgressLoggerFactory + override fun init() { super.init() + mappedServerJar.set(layout.cache.resolve(paperTaskOutput("mappedServerJar", "jar"))) + reobfMappings.set(layout.cache.resolve(paperTaskOutput("reobfMappings", "tiny"))) devBundle.from(project.configurations.named(DEV_BUNDLE_CONFIG)) decompilerConfig.from(project.configurations.named(DECOMPILER_CONFIG)) paramMappingsConfig.from(project.configurations.named(PARAM_MAPPINGS_CONFIG)) @@ -99,6 +109,7 @@ abstract class UserdevSetupTask : JavaLauncherTask() { launcher.get(), layout, logger, + progressLoggerFactory, decompilerConfig, paramMappingsConfig, macheDecompilerConfig, @@ -110,6 +121,14 @@ abstract class UserdevSetupTask : JavaLauncherTask() { macheCodebookConfig, ) - setupService.get().combinedOrClassesJar(context) + val took = measureNanoTime { + setupService.get().generateCombinedOrClassesJar( + context, + mappedServerJar.path.createParentDirectories(), + legacyPaperclipResult.pathOrNull?.createParentDirectories(), + ) + setupService.get().extractReobfMappings(reobfMappings.path.createParentDirectories()) + } + logger.lifecycle("Completed setup in ${formatNs(took)}") } } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/AccessTransformMinecraftAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/AccessTransformMinecraftAction.kt new file mode 100644 index 000000000..924f339b2 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/AccessTransformMinecraftAction.kt @@ -0,0 +1,63 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import io.papermc.paperweight.tasks.* +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.StringValue +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.util.* +import kotlin.io.path.* +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.workers.WorkerExecutor + +class AccessTransformMinecraftAction( + @Input private val javaLauncher: Value, + private val workerExecutor: WorkerExecutor, + @Input private val bundleZip: FileValue, + @Input val atPath: StringValue, + @Input private val inputJar: FileValue, + @Output val outputJar: FileValue, +) : WorkDispatcher.Action { + override fun execute() { + val atTmp = outputJar.get().resolveSibling("tmp.at").cleanFile() + bundleZip.get().openZipSafe().use { fs -> + val at = fs.getPath(atPath.get()) + at.copyTo(atTmp) + } + try { + applyAccessTransform( + inputJarPath = inputJar.get(), + outputJarPath = outputJar.get(), + atFilePath = atTmp, + workerExecutor = workerExecutor, + launcher = javaLauncher.get(), + ).await() + } finally { + atTmp.deleteIfExists() + } + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ApplyDevBundlePatchesAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ApplyDevBundlePatchesAction.kt new file mode 100644 index 000000000..ee72c6b61 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ApplyDevBundlePatchesAction.kt @@ -0,0 +1,114 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import codechicken.diffpatch.cli.PatchOperation +import codechicken.diffpatch.util.LogLevel +import codechicken.diffpatch.util.archiver.ArchiveFormat +import io.papermc.paperweight.PaperweightException +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.StringValue +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.siblingLogFile +import io.papermc.paperweight.util.* +import java.io.PrintStream +import java.nio.file.Files +import kotlin.io.path.* +import kotlin.streams.asSequence + +class ApplyDevBundlePatchesAction( + @Input private val decompiledJar: FileValue, + @Input val bundleZip: FileValue, + @Input val patchesPath: StringValue, + @Output val outputJar: FileValue, + @Input private val patchedJar: FileValue? = null, +) : WorkDispatcher.Action { + override fun execute() { + val tempPatchDir = findOutputDir(outputJar.get()) + val outputDir = findOutputDir(outputJar.get()) + val log = outputJar.get().siblingLogFile() + + try { + bundleZip.get().openZipSafe().use { fs -> + val patchesDir = fs.getPath(patchesPath.get()) + val (patches, newFiles) = Files.walk(patchesDir).use { stream -> + stream.asSequence() + .filter { it.isRegularFile() } + .partition { it.name.endsWith(".patch") } + } + for (patch in patches) { + relativeCopy(patchesDir, patch, tempPatchDir) + } + + ensureDeleted(log) + PrintStream(log.toFile(), Charsets.UTF_8).use { logOut -> + val op = PatchOperation.builder() + .logTo(logOut) + .level(LogLevel.ALL) + .summary(true) + .basePath(decompiledJar.get(), ArchiveFormat.ZIP) + .patchesPath(tempPatchDir) + .outputPath(outputDir) + .build() + try { + op.operate().throwOnError() + } catch (ex: Exception) { + throw PaperweightException( + "Failed to apply dev bundle patches. See the log file at '${log.toFile()}' for more details.", + ex + ) + } + } + + for (file in newFiles) { + relativeCopy(patchesDir, file, outputDir) + } + } + + ensureDeleted(outputJar.get()) + zip(outputDir, outputJar.get()) + + // Merge classes and resources in + patchedJar?.let { patched -> + outputJar.get().openZip().use { fs -> + val out = fs.getPath("/") + patched.get().openZip().use { patchedFs -> + val patchedRoot = patchedFs.getPath("/") + + patchedRoot.walk() + .filter { it.isRegularFile() } + .forEach { file -> + val copyTo = out.resolve(file.relativeTo(patchedRoot).invariantSeparatorsPathString) + copyTo.createParentDirectories() + file.copyTo(copyTo) + } + } + } + } + } finally { + ensureDeleted(outputDir, tempPatchDir) + } + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/DecompileMinecraftAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/DecompileMinecraftAction.kt new file mode 100644 index 000000000..683dccf66 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/DecompileMinecraftAction.kt @@ -0,0 +1,59 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import io.papermc.paperweight.tasks.* +import io.papermc.paperweight.userdev.internal.action.DirectoryValue +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.ListValue +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.jars +import io.papermc.paperweight.userdev.internal.util.siblingLogFile +import kotlin.io.path.* +import org.gradle.jvm.toolchain.JavaLauncher + +class DecompileMinecraftAction( + @Input private val javaLauncher: Value, + @Input private val inputJar: FileValue, + @Output val outputJar: FileValue, + @Input private val minecraftLibraryJars: DirectoryValue, + @Input val decompileArgs: ListValue, + @Input val decompiler: FileCollectionValue, +) : WorkDispatcher.Action { + override fun execute() { + runDecompiler( + argsList = decompileArgs.get(), + logFile = outputJar.get().siblingLogFile(), + workingDir = outputJar.get().parent.createDirectories(), + executable = decompiler.get(), + inputJar = inputJar.get(), + libraries = minecraftLibraryJars.get().jars(), + outputJar = outputJar.get(), + javaLauncher = javaLauncher.get() + ) + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/AccessTransformMinecraft.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ExtractFromBundlerAction.kt similarity index 51% rename from paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/AccessTransformMinecraft.kt rename to paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ExtractFromBundlerAction.kt index 2ec6d1aa9..9a2ae7eaf 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/AccessTransformMinecraft.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/ExtractFromBundlerAction.kt @@ -20,29 +20,32 @@ * USA */ -package io.papermc.paperweight.userdev.internal.setup.step +package io.papermc.paperweight.userdev.internal.setup.action import io.papermc.paperweight.tasks.* -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import java.nio.file.Path +import io.papermc.paperweight.userdev.internal.action.DirectoryValue +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher -class AccessTransformMinecraft( - @Input private val at: Path, - @Input private val inputJar: Path, - @Output private val outputJar: Path, -) : SetupStep { - override val name: String = "access transform minecraft server jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - applyAccessTransform( - inputJarPath = inputJar, - outputJarPath = outputJar, - atFilePath = at, - workerExecutor = context.workerExecutor, - launcher = context.javaLauncher - ).await() +class ExtractFromBundlerAction( + @Input + val mojangJar: FileValue, + @Output + val vanillaServerJar: FileValue, + @Output + val minecraftLibraryJars: DirectoryValue, +) : WorkDispatcher.Action { + override fun execute() { + ServerBundler.extractFromBundler( + mojangJar.get(), + vanillaServerJar.get(), + minecraftLibraryJars.get(), + null, + null, + null, + null, + ) } } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FilterPaperShadowJar.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FilterPaperShadowJarAction.kt similarity index 75% rename from paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FilterPaperShadowJar.kt rename to paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FilterPaperShadowJarAction.kt index 0ce5e7031..b2d1f595e 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FilterPaperShadowJar.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FilterPaperShadowJarAction.kt @@ -20,33 +20,25 @@ * USA */ -package io.papermc.paperweight.userdev.internal.setup.step +package io.papermc.paperweight.userdev.internal.setup.action -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher import io.papermc.paperweight.util.* import java.nio.file.Path -import kotlin.io.path.isRegularFile -import kotlin.io.path.name -import kotlin.io.path.pathString +import kotlin.io.path.* -class FilterPaperShadowJar( - @Input private val sourcesJar: Path, - @Input private val inputJar: Path, - @Output private val outputJar: Path, - private val relocations: List, -) : SetupStep { - override val name: String = "filter and merge mojang mapped paper jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - filterPaperJar(sourcesJar, inputJar, outputJar, relocations) - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(gson.toJson(relocations)) +class FilterPaperShadowJarAction( + @Input private val sourcesJar: FileValue, + @Input private val inputJar: FileValue, + @Output val outputJar: FileValue, + @Input val relocations: Value>, +) : WorkDispatcher.Action { + override fun execute() { + filterPaperJar(sourcesJar.get(), inputJar.get(), outputJar.get(), relocations.get()) } private fun filterPaperJar( diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FilterVanillaJar.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FilterVanillaJarAction.kt similarity index 57% rename from paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FilterVanillaJar.kt rename to paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FilterVanillaJarAction.kt index ff51aed0f..30ef74366 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FilterVanillaJar.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FilterVanillaJarAction.kt @@ -20,23 +20,21 @@ * USA */ -package io.papermc.paperweight.userdev.internal.setup.step +package io.papermc.paperweight.userdev.internal.setup.action -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.ListValue +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher import io.papermc.paperweight.util.* -import java.nio.file.Path -class FilterVanillaJar( - @Input private val vanillaJar: Path, - @Input private val includes: List, - @Output private val outputJar: Path, -) : SetupStep { - override val name: String = "filter vanilla server jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - filterJar(vanillaJar, outputJar, includes) +class FilterVanillaJarAction( + @Input private val vanillaJar: FileValue, + @Input val includes: ListValue, + @Output val outputJar: FileValue, +) : WorkDispatcher.Action { + override fun execute() { + filterJar(vanillaJar.get(), outputJar.get(), includes.get()) } } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FixMinecraftJar.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FixMinecraftJarAction.kt similarity index 51% rename from paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FixMinecraftJar.kt rename to paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FixMinecraftJarAction.kt index 62f489e91..e2457f36e 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/FixMinecraftJar.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/FixMinecraftJarAction.kt @@ -20,30 +20,32 @@ * USA */ -package io.papermc.paperweight.userdev.internal.setup.step +package io.papermc.paperweight.userdev.internal.setup.action import io.papermc.paperweight.tasks.* -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import java.nio.file.Path +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.workers.WorkerExecutor -class FixMinecraftJar( - @Input private val inputJar: Path, - @Output private val outputJar: Path, - @Input private val vanillaServerJar: Path, +class FixMinecraftJarAction( + @Input private val javaLauncher: Value, + private val workerExecutor: WorkerExecutor, + @Input private val inputJar: FileValue, + @Output val outputJar: FileValue, + @Input private val vanillaServerJar: FileValue, private val useLegacyParameterAnnotationFixer: Boolean = false, -) : SetupStep { - override val name: String = "fix minecraft server jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { +) : WorkDispatcher.Action { + override fun execute() { fixJar( - workerExecutor = context.workerExecutor, - launcher = context.javaLauncher, - vanillaJarPath = vanillaServerJar, - inputJarPath = inputJar, - outputJarPath = outputJar, + workerExecutor = workerExecutor, + launcher = javaLauncher.get(), + vanillaJarPath = vanillaServerJar.get(), + inputJarPath = inputJar.get(), + outputJarPath = outputJar.get(), useLegacyParameterAnnotationFixer = useLegacyParameterAnnotationFixer ).await() } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/GenerateMappingsAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/GenerateMappingsAction.kt new file mode 100644 index 000000000..e7aa71729 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/GenerateMappingsAction.kt @@ -0,0 +1,57 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import io.papermc.paperweight.tasks.* +import io.papermc.paperweight.userdev.internal.action.DirectoryValue +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.jars +import org.gradle.jvm.toolchain.JavaLauncher +import org.gradle.workers.WorkerExecutor + +class GenerateMappingsAction( + @Input private val javaLauncher: Value, + private val workerExecutor: WorkerExecutor, + @Input private val serverMappings: FileValue, + @Input private val filteredVanillaJar: FileValue, + @Input val paramMappings: FileCollectionValue, + @Input private val minecraftLibraryJars: DirectoryValue, + @Output val outputMappings: FileValue, +) : WorkDispatcher.Action { + override fun execute() { + generateMappings( + vanillaJarPath = filteredVanillaJar.get(), + libraryPaths = minecraftLibraryJars.get().jars(), + vanillaMappingsPath = serverMappings.get(), + paramMappingsPath = paramMappings.get().singleFile.toPath(), + outputMappingsPath = outputMappings.get(), + workerExecutor = workerExecutor, + launcher = javaLauncher.get() + ).await() + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftAction.kt new file mode 100644 index 000000000..13b3a5c30 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftAction.kt @@ -0,0 +1,64 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import io.papermc.paperweight.tasks.* +import io.papermc.paperweight.userdev.internal.action.DirectoryValue +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.ListValue +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.jars +import io.papermc.paperweight.userdev.internal.util.siblingLogFile +import io.papermc.paperweight.util.constants.* +import kotlin.io.path.* +import org.gradle.jvm.toolchain.JavaLauncher + +class RemapMinecraftAction( + @Input private val javaLauncher: Value, + @Input val minecraftRemapArgs: ListValue, + @Input private val filteredVanillaJar: FileValue, + @Input private val minecraftLibraryJars: DirectoryValue, + @Input private val mappings: FileValue, + @Input val remapper: FileCollectionValue, + @Output val outputJar: FileValue, +) : WorkDispatcher.Action { + override fun execute() { + TinyRemapper.run( + argsList = minecraftRemapArgs.get(), + logFile = outputJar.get().siblingLogFile(), + inputJar = filteredVanillaJar.get(), + mappingsFile = mappings.get(), + fromNamespace = OBF_NAMESPACE, + toNamespace = DEOBF_NAMESPACE, + remapClasspath = minecraftLibraryJars.get().jars(), + remapper = remapper.get(), + outputJar = outputJar.get(), + launcher = javaLauncher.get(), + workingDir = outputJar.get().parent.createDirectories(), + ) + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftMacheAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftMacheAction.kt new file mode 100644 index 000000000..de342abea --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RemapMinecraftMacheAction.kt @@ -0,0 +1,68 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import io.papermc.paperweight.tasks.mache.macheRemapJar +import io.papermc.paperweight.userdev.internal.action.DirectoryValue +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.ListValue +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.jars +import io.papermc.paperweight.util.* +import kotlin.io.path.* +import org.gradle.jvm.toolchain.JavaLauncher + +class RemapMinecraftMacheAction( + @Input private val javaLauncher: Value, + @Input val minecraftRemapArgs: ListValue, + @Input val vanillaJar: FileValue, + @Input private val minecraftLibraryJars: DirectoryValue, + @Input val mappings: FileValue, + @Input val paramMappings: FileCollectionValue, + @Input val constants: FileCollectionValue, + @Input val codebook: FileCollectionValue, + @Input val remapper: FileCollectionValue, + @Output val outputJar: FileValue, +) : WorkDispatcher.Action { + override fun execute() { + val temp = outputJar.get().parent.resolve("work").createDirectories() + macheRemapJar( + javaLauncher.get(), + codebook.get(), + outputJar.get(), + minecraftRemapArgs.get(), + temp, + remapper.get(), + mappings.get(), + paramMappings.get().singleFile.toPath(), + constants.get().singleOrNull()?.toPath(), + vanillaJar.get(), + minecraftLibraryJars.get().jars(), + ) + temp.filesMatchingRecursive("*.jar").forEach { it.deleteIfExists() } // TODO: Codebook leaves a jar laying around + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RunPaperclip.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RunPaperclipAction.kt similarity index 64% rename from paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RunPaperclip.kt rename to paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RunPaperclipAction.kt index 1b4da1172..c20393243 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RunPaperclip.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/RunPaperclipAction.kt @@ -20,41 +20,44 @@ * USA */ -package io.papermc.paperweight.userdev.internal.setup +package io.papermc.paperweight.userdev.internal.setup.action import com.google.gson.JsonObject -import io.papermc.paperweight.userdev.internal.setup.step.Input -import io.papermc.paperweight.userdev.internal.setup.step.Output -import io.papermc.paperweight.userdev.internal.setup.step.SetupStep -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.StringValue +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.siblingLogFile import io.papermc.paperweight.util.* import io.papermc.paperweight.util.data.* import java.nio.file.Path import kotlin.io.path.* - -class RunPaperclip( - @Input private val paperclip: Path, - @Output private val outputJar: Path, - @Input private val mojangJar: Path, - @Input private val minecraftVersion: String, +import org.gradle.jvm.toolchain.JavaLauncher + +class RunPaperclipAction( + @Input private val javaLauncher: Value, + @Input val bundleZip: FileValue, + @Input val paperclipPath: StringValue, + @Output val outputJar: FileValue, + @Input val mojangJar: FileValue, + @Input val minecraftVersion: StringValue, private val bundler: Boolean = true, -) : SetupStep { - override val name: String = "apply mojang mapped paperclip patch" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - patchPaperclip(context, paperclip, outputJar, mojangJar, minecraftVersion, bundler) - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.includePaperweightHash = false +) : WorkDispatcher.Action { + override fun execute() { + val paperclip = outputJar.get().resolveSibling("paperclip-tmp.jar").cleanFile() + bundleZip.get().openZipSafe().use { fs -> + fs.getPath(paperclipPath.get()).copyTo(paperclip) + } + try { + patchPaperclip(paperclip, outputJar.get(), mojangJar.get(), minecraftVersion.get(), bundler) + } finally { + paperclip.deleteIfExists() + } } private fun patchPaperclip( - context: SetupHandler.ExecutionContext, paperclip: Path, outputJar: Path, mojangJar: Path, @@ -63,7 +66,7 @@ class RunPaperclip( ) { val logFile = outputJar.siblingLogFile() - val work = createTempDirectory() + val work = createTempDirectory(outputJar.parent.createDirectories(), "paperclip") ensureDeleted(logFile) // Copy in mojang jar, so we don't download it twice @@ -71,8 +74,8 @@ class RunPaperclip( cache.createDirectories() mojangJar.copyTo(cache.resolve("mojang_$minecraftVersion.jar")) - context.javaLauncher.runJar( - classpath = context.layout.files(paperclip), + javaLauncher.get().runJar( + classpath = listOf(paperclip.toFile()), workingDir = work, logFile = logFile, jvmArgs = listOf("-Dpaperclip.patchonly=true"), @@ -89,8 +92,8 @@ class RunPaperclip( } private fun handleBundler(paperclip: Path, work: Path, outputJar: Path) { - paperclip.openZip().use { fs -> - val root = fs.rootDirectories.single() + paperclip.openZipSafe().use { fs -> + val root = fs.getPath("/") val serverVersionJson = root.resolve(FileEntry.VERSION_JSON) val versionId = gson.fromJson(serverVersionJson)["id"].asString diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/SetupMacheSourcesAction.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/SetupMacheSourcesAction.kt new file mode 100644 index 000000000..018029e77 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/SetupMacheSourcesAction.kt @@ -0,0 +1,90 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import codechicken.diffpatch.cli.PatchOperation +import codechicken.diffpatch.util.LogLevel +import codechicken.diffpatch.util.archiver.ArchiveFormat +import io.papermc.paperweight.PaperweightException +import io.papermc.paperweight.tasks.mache.macheDecompileJar +import io.papermc.paperweight.userdev.internal.action.DirectoryValue +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.ListValue +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.Value +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.util.jars +import io.papermc.paperweight.util.* +import java.io.PrintStream +import kotlin.io.path.* +import org.gradle.jvm.toolchain.JavaLauncher + +class SetupMacheSourcesAction( + @Input private val javaLauncher: Value, + @Input private val inputJar: FileValue, + @Output val outputJar: FileValue, + @Input private val minecraftLibraryJars: DirectoryValue, + @Input val decompileArgs: ListValue, + @Input val decompiler: FileCollectionValue, + @Input val mache: FileCollectionValue, +) : WorkDispatcher.Action { + override fun execute() { + val tmpDir = outputJar.get().parent.createDirectories() + + // Decompile + val tempOut = outputJar.get().resolveSibling("decompile.jar") + macheDecompileJar( + tempOut, + minecraftLibraryJars.get().jars(), + decompileArgs.get(), + inputJar.get(), + javaLauncher.get(), + decompiler.get(), + tmpDir, + ) + + // Apply mache patches + outputJar.get().cleanFile() + val log = tmpDir.resolve("${outputJar.get().name}.log") + ensureDeleted(log) + val result = PrintStream(log.toFile(), Charsets.UTF_8).use { logOut -> + PatchOperation.builder() + .logTo(logOut) + .basePath(tempOut, ArchiveFormat.ZIP) + .outputPath(outputJar.get(), ArchiveFormat.ZIP) + .patchesPath(mache.get().singleFile.toPath(), ArchiveFormat.ZIP) + .patchesPrefix("patches") + .level(LogLevel.ALL) + .summary(true) + .build() + .operate() + } + tempOut.cleanFile() + + if (result.exit != 0) { + throw PaperweightException("Failed to apply mache patches. See the log file at '${log.toFile()}' for more details.") + } + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/VanillaServerDownloads.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/VanillaServerDownloads.kt new file mode 100644 index 000000000..76f63ed47 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/action/VanillaServerDownloads.kt @@ -0,0 +1,97 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.setup.action + +import io.papermc.paperweight.DownloadService +import io.papermc.paperweight.PaperweightException +import io.papermc.paperweight.userdev.internal.action.FileValue +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.action.StringValue +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.util.* +import io.papermc.paperweight.util.constants.* +import io.papermc.paperweight.util.data.* +import java.nio.file.Path +import kotlin.io.path.* +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking + +class VanillaServerDownloads( + @Input + val minecraftVersion: StringValue, + @Output + val serverJar: FileValue, + @Output + val serverMappings: FileValue, + private val downloadService: DownloadService, +) : WorkDispatcher.Action { + override fun execute() { + val tmp = createTempDirectory(serverJar.get().parent.createDirectories(), "vanilla-downloads") + + val mcManifestPath = tmp.resolve("mc.json") + downloadService.downloadFile(MC_MANIFEST_URL, mcManifestPath, null) + val mcManifest = gson.fromJson(mcManifestPath) + + val versionManifestPath = tmp.resolve("version.json") + val ver = mcManifest.versions.firstOrNull { it.id == minecraftVersion.get() } + ?: throw PaperweightException("Could not find Minecraft version '${minecraftVersion.get()}' in the downloaded manifest.") + downloadService.downloadFile( + ver.url, + versionManifestPath, + expectedHash = ver.hash() + ) + val versionManifest: MinecraftVersionManifest = gson.fromJson(versionManifestPath) + + val dispatcher = ioDispatcher("VanillaServerDownloads") + runBlocking { + launch(dispatcher) { + val serverTmp = tmp.resolve("server.jar") + downloadService.downloadFile( + versionManifest.serverDownload().url, + serverTmp, + expectedHash = versionManifest.serverDownload().hash() + ) + serverTmp.copyTo(serverJar.get(), overwrite = true) + } + launch(dispatcher) { + val mappingsTmp = tmp.resolve("mappings.txt") + downloadService.downloadFile( + versionManifest.serverMappingsDownload().url, + mappingsTmp, + expectedHash = versionManifest.serverMappingsDownload().hash() + ) + mappingsTmp.copyTo(serverMappings.get(), overwrite = true) + } + } + dispatcher.close() + + tmp.deleteRecursive() + } + + private fun DownloadService.downloadFile(remote: Any, destination: Path, expectedHash: Hash?) { + destination.parent.createDirectories() + destination.deleteIfExists() + download(remote, destination, expectedHash) + } +} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ApplyDevBundlePatches.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ApplyDevBundlePatches.kt deleted file mode 100644 index f8a29d0b0..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ApplyDevBundlePatches.kt +++ /dev/null @@ -1,119 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import codechicken.diffpatch.cli.PatchOperation -import codechicken.diffpatch.util.LogLevel -import codechicken.diffpatch.util.archiver.ArchiveFormat -import io.papermc.paperweight.PaperweightException -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.hashDirectory -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile -import io.papermc.paperweight.util.* -import java.io.PrintStream -import java.nio.file.Files -import java.nio.file.Path -import kotlin.io.path.* -import kotlin.streams.asSequence - -class ApplyDevBundlePatches( - @Input private val decompiledJar: Path, - private val devBundlePatches: Path, - @Output private val outputJar: Path, - @Input private val patchedJar: Path? = null, -) : SetupStep { - override val name: String - get() = if (patchedJar == null) "apply patches to decompiled jar" else "apply source patches and merge jars" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - val tempPatchDir = findOutputDir(outputJar) - val outputDir = findOutputDir(outputJar) - val log = outputJar.siblingLogFile() - - try { - val (patches, newFiles) = Files.walk(devBundlePatches).use { stream -> - stream.asSequence() - .filter { it.isRegularFile() } - .partition { it.name.endsWith(".patch") } - } - for (patch in patches) { - relativeCopy(devBundlePatches, patch, tempPatchDir) - } - - ensureDeleted(log) - PrintStream(log.toFile(), Charsets.UTF_8).use { logOut -> - val op = PatchOperation.builder() - .logTo(logOut) - .level(LogLevel.ALL) - .summary(true) - .basePath(decompiledJar, ArchiveFormat.ZIP) - .patchesPath(tempPatchDir) - .outputPath(outputDir) - .build() - try { - op.operate().throwOnError() - } catch (ex: Exception) { - throw PaperweightException( - "Failed to apply dev bundle patches. See the log file at '${log.toFile()}' for more details.", - ex - ) - } - } - - for (file in newFiles) { - relativeCopy(devBundlePatches, file, outputDir) - } - - ensureDeleted(outputJar) - zip(outputDir, outputJar) - - // Merge classes and resources in - patchedJar?.let { patched -> - outputJar.openZip().use { fs -> - val out = fs.getPath("/") - patched.openZip().use { patchedFs -> - val patchedRoot = patchedFs.getPath("/") - - patchedRoot.walk() - .filter { it.isRegularFile() } - .forEach { file -> - val copyTo = out.resolve(file.relativeTo(patchedRoot).invariantSeparatorsPathString) - copyTo.createParentDirectories() - file.copyTo(copyTo) - } - } - } - } - } finally { - ensureDeleted(outputDir, tempPatchDir) - } - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(hashDirectory(devBundlePatches)) - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/DecompileMinecraft.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/DecompileMinecraft.kt deleted file mode 100644 index 41e53486d..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/DecompileMinecraft.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.tasks.runDecompiler -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile -import java.nio.file.Path -import org.gradle.api.file.FileCollection - -class DecompileMinecraft( - @Input private val inputJar: Path, - @Output private val outputJar: Path, - private val cache: Path, - private val minecraftLibraryJars: () -> List, - @Input private val decompileArgs: List, - private val decompiler: FileCollection, -) : SetupStep { - override val name: String = "decompile transformed minecraft server jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - runDecompiler( - argsList = decompileArgs, - logFile = outputJar.siblingLogFile(), - workingDir = cache, - executable = decompiler, - inputJar = inputJar, - libraries = minecraftLibraryJars(), - outputJar = outputJar, - javaLauncher = context.javaLauncher - ) - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(minecraftLibraryJars()) - builder.include(decompiler.map { it.toPath() }) - builder.includePaperweightHash = false - } - - companion object { - fun create( - context: SetupHandler.ExecutionContext, - inputJar: Path, - outputJar: Path, - cache: Path, - minecraftLibraryJars: () -> List, - decompileArgs: List, - ): DecompileMinecraft { - val decompiler = context.decompilerConfig.also { it.files.size } // resolve decompiler - return DecompileMinecraft(inputJar, outputJar, cache, minecraftLibraryJars, decompileArgs, decompiler) - } - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ExtractFromBundlerStep.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ExtractFromBundlerStep.kt deleted file mode 100644 index 79e587a87..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/ExtractFromBundlerStep.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.tasks.ServerBundler -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.util.constants.paperSetupOutput -import java.nio.file.Path - -class ExtractFromBundlerStep( - cache: Path, - private val vanillaSteps: VanillaSteps, - private val vanillaServerJar: Path, - private val minecraftLibraryJars: Path, - private val listMinecraftLibraryJars: () -> List, -) : SetupStep { - override val name: String = "extract libraries and server from downloaded jar" - - override val hashFile: Path = cache.resolve(paperSetupOutput("extractFromServerBundler", "hashes")) - - override fun run(context: SetupHandler.ExecutionContext) { - ServerBundler.extractFromBundler( - vanillaSteps.mojangJar, - vanillaServerJar, - minecraftLibraryJars, - null, - null, - null, - null, - ) - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(vanillaSteps.mojangJar, vanillaServerJar) - builder.include(listMinecraftLibraryJars()) - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/GenerateMappingsStep.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/GenerateMappingsStep.kt deleted file mode 100644 index f7bc503bd..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/GenerateMappingsStep.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.tasks.* -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import java.nio.file.Path - -class GenerateMappingsStep( - private val vanillaSteps: VanillaSteps, - @Input private val filteredVanillaJar: Path, - @Input private val paramMappings: Path, - private val minecraftLibraryJars: () -> List, - @Output private val outputMappings: Path, -) : SetupStep { - override val name: String = "generate mappings" - - override val hashFile: Path = outputMappings.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - generateMappings( - vanillaJarPath = filteredVanillaJar, - libraryPaths = minecraftLibraryJars(), - vanillaMappingsPath = vanillaSteps.serverMappings, - paramMappingsPath = paramMappings, - outputMappingsPath = outputMappings, - workerExecutor = context.workerExecutor, - launcher = context.javaLauncher - ).await() - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(minecraftLibraryJars()) - builder.include(vanillaSteps.serverMappings) - } - - companion object { - fun create( - context: SetupHandler.ExecutionContext, - vanillaSteps: VanillaSteps, - filteredVanillaJar: Path, - minecraftLibraryJars: () -> List, - outputMappings: Path, - ): GenerateMappingsStep { - vanillaSteps.downloadServerMappings() - - // resolve param mappings - val paramMappings = context.paramMappingsConfig.singleFile.toPath() - - return GenerateMappingsStep(vanillaSteps, filteredVanillaJar, paramMappings, minecraftLibraryJars, outputMappings) - } - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/MinecraftSourcesMache.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/MinecraftSourcesMache.kt deleted file mode 100644 index e6af1c664..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/MinecraftSourcesMache.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import codechicken.diffpatch.cli.PatchOperation -import codechicken.diffpatch.util.LoggingOutputStream -import codechicken.diffpatch.util.archiver.ArchiveFormat -import io.papermc.paperweight.tasks.mache.macheDecompileJar -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import io.papermc.paperweight.util.cleanFile -import java.nio.file.Path -import kotlin.io.path.name -import org.gradle.api.file.FileCollection -import org.gradle.api.logging.LogLevel - -class MinecraftSourcesMache( - @Input private val inputJar: Path, - @Output private val outputJar: Path, - private val cache: Path, - private val minecraftLibraryJars: () -> List, - @Input private val decompileArgs: List, - private val decompiler: FileCollection, - private val mache: FileCollection, -) : SetupStep { - override val name: String = "decompile and setup sources with mache" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - // Decompile - val tempOut = outputJar.resolveSibling("${outputJar.name}.tmp") - macheDecompileJar( - tempOut, - minecraftLibraryJars(), - decompileArgs, - inputJar, - context.javaLauncher, - decompiler, - cache, - ) - - // Apply mache patches - outputJar.cleanFile() - val result = PatchOperation.builder() - .logTo(LoggingOutputStream(context.logger, LogLevel.LIFECYCLE)) - .basePath(tempOut, ArchiveFormat.ZIP) - .outputPath(outputJar, ArchiveFormat.ZIP) - .patchesPath(mache.singleFile.toPath(), ArchiveFormat.ZIP) - .patchesPrefix("patches") - .level(codechicken.diffpatch.util.LogLevel.INFO) - .build() - .operate() - tempOut.cleanFile() - - if (result.exit != 0) { - throw Exception("Failed to apply ${result.summary.failedMatches} mache patches") - } - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(minecraftLibraryJars()) - builder.include(decompiler.map { it.toPath() }) - builder.include(mache.map { it.toPath() }) - builder.includePaperweightHash = false - } - - companion object { - fun create( - context: SetupHandler.ExecutionContext, - inputJar: Path, - outputJar: Path, - cache: Path, - minecraftLibraryJars: () -> List, - decompileArgs: List, - ): MinecraftSourcesMache { - // resolve dependencies - val decompiler = context.macheDecompilerConfig.also { it.files.size } - val mache = context.macheConfig.also { it.files.size } - return MinecraftSourcesMache(inputJar, outputJar, cache, minecraftLibraryJars, decompileArgs, decompiler, mache) - } - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraft.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraft.kt deleted file mode 100644 index bd6f57281..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraft.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.tasks.* -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile -import io.papermc.paperweight.util.constants.* -import java.nio.file.Path -import org.gradle.api.file.FileCollection - -class RemapMinecraft( - @Input private val minecraftRemapArgs: List, - @Input private val filteredVanillaJar: Path, - private val minecraftLibraryJars: () -> List, - @Input private val mappings: Path, - private val remapper: FileCollection, - @Output private val outputJar: Path, - private val cache: Path, -) : SetupStep { - override val name: String = "remap minecraft server jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - TinyRemapper.run( - argsList = minecraftRemapArgs, - logFile = outputJar.siblingLogFile(), - inputJar = filteredVanillaJar, - mappingsFile = mappings, - fromNamespace = OBF_NAMESPACE, - toNamespace = DEOBF_NAMESPACE, - remapClasspath = minecraftLibraryJars(), - remapper = remapper, - outputJar = outputJar, - launcher = context.javaLauncher, - workingDir = cache - ) - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.includePaperweightHash = false - builder.include(minecraftLibraryJars()) - builder.include(remapper.map { it.toPath() }) - } - - companion object { - fun create( - context: SetupHandler.ExecutionContext, - minecraftRemapArgs: List, - filteredVanillaJar: Path, - minecraftLibraryJars: () -> List, - mappings: Path, - outputJar: Path, - cache: Path, - ): RemapMinecraft { - val remapper = context.remapperConfig.also { it.files.size } // resolve remapper - return RemapMinecraft( - minecraftRemapArgs, - filteredVanillaJar, - minecraftLibraryJars, - mappings, - remapper, - outputJar, - cache, - ) - } - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraftMache.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraftMache.kt deleted file mode 100644 index 34c5c8de1..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/RemapMinecraftMache.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.tasks.mache.macheRemapJar -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile -import io.papermc.paperweight.util.deleteRecursive -import java.nio.file.Path -import kotlin.io.path.createTempDirectory -import org.gradle.api.file.FileCollection - -class RemapMinecraftMache( - @Input private val minecraftRemapArgs: List, - @Input private val vanillaJar: Path, - private val minecraftLibraryJars: () -> List, - @Input private val mappings: Path, - @Input private val paramMappings: Path, - @Input private val constants: Path?, - private val codebook: FileCollection, - private val remapper: FileCollection, - @Output private val outputJar: Path, - private val cache: Path, -) : SetupStep { - override val name: String = "remap minecraft server jar" - - override val hashFile: Path = outputJar.siblingHashesFile() - - override fun run(context: SetupHandler.ExecutionContext) { - val temp = createTempDirectory(cache, "remap") - macheRemapJar( - context.javaLauncher, - codebook, - outputJar, - minecraftRemapArgs, - temp, - remapper, - mappings, - paramMappings, - constants, - vanillaJar, - minecraftLibraryJars() - ) - temp.deleteRecursive() - } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.includePaperweightHash = false - builder.include(minecraftLibraryJars()) - builder.include(remapper.map { it.toPath() }) - builder.include(codebook.map { it.toPath() }) - } - - companion object { - fun create( - context: SetupHandler.ExecutionContext, - minecraftRemapArgs: List, - vanillaJar: Path, - minecraftLibraryJars: () -> List, - mappings: Path, - outputJar: Path, - cache: Path, - ): RemapMinecraftMache { - // resolve dependencies - val remapper = context.macheRemapperConfig.also { it.files.size } - val paramMappings = context.macheParamMappingsConfig.singleFile.toPath() - val constants = context.macheConstantsConfig.files.singleOrNull()?.toPath() - val codebook = context.macheCodebookConfig.also { it.files.size } - return RemapMinecraftMache( - minecraftRemapArgs, - vanillaJar, - minecraftLibraryJars, - mappings, - paramMappings, - constants, - codebook, - remapper, - outputJar, - cache, - ) - } - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/VanillaSteps.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/VanillaSteps.kt deleted file mode 100644 index 68b88ac01..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/VanillaSteps.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.DownloadService -import io.papermc.paperweight.PaperweightException -import io.papermc.paperweight.userdev.internal.setup.util.* -import io.papermc.paperweight.util.* -import io.papermc.paperweight.util.constants.* -import io.papermc.paperweight.util.data.* -import java.nio.file.Path -import org.gradle.kotlin.dsl.* - -class VanillaSteps( - private val minecraftVersion: String, - private val cache: Path, - private val downloadService: DownloadService, - private val bundleChanged: Boolean, -) { - private val versionManifest: MinecraftVersionManifest by lazy { setupMinecraftVersionManifest() } - val mojangJar: Path = cache.resolve(paperSetupOutput("downloadServerJar", "jar")) - val serverMappings: Path = cache.resolve(SERVER_MAPPINGS) - - fun downloadVanillaServerJar(): DownloadResult = downloadService.download( - "vanilla minecraft server jar", - versionManifest.serverDownload().url, - mojangJar, - expectedHash = versionManifest.serverDownload().hash() - ) - - fun downloadServerMappings(): DownloadResult = downloadService.download( - "mojang server mappings", - versionManifest.serverMappingsDownload().url, - serverMappings, - expectedHash = versionManifest.serverMappingsDownload().hash() - ) - - private fun downloadMinecraftManifest(force: Boolean): DownloadResult = - downloadService.download("minecraft manifest", MC_MANIFEST_URL, cache.resolve(MC_MANIFEST), force) - .mapData { gson.fromJson(it.path) } - - private fun setupMinecraftVersionManifest(): MinecraftVersionManifest { - var minecraftManifest = downloadMinecraftManifest(bundleChanged) - if (!minecraftManifest.didDownload && minecraftManifest.data.versions.none { it.id == minecraftVersion }) { - minecraftManifest = downloadMinecraftManifest(true) - } - - val ver = minecraftManifest.data.versions.firstOrNull { it.id == minecraftVersion } - ?: throw PaperweightException("Could not find Minecraft version '$minecraftVersion' in the downloaded manifest.") - val minecraftVersionManifestJson = downloadService.download( - "minecraft version manifest", - ver.url, - cache.resolve(VERSION_JSON), - expectedHash = ver.hash() - ) - return gson.fromJson(minecraftVersionManifestJson.path) - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/steps.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/steps.kt deleted file mode 100644 index a20ffba5f..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/step/steps.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.step - -import io.papermc.paperweight.PaperweightException -import io.papermc.paperweight.userdev.internal.setup.SetupHandler -import io.papermc.paperweight.userdev.internal.setup.UserdevSetup -import io.papermc.paperweight.userdev.internal.setup.util.HashFunction -import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder -import io.papermc.paperweight.userdev.internal.setup.util.buildHashFunction -import java.nio.file.Path -import java.util.concurrent.ConcurrentHashMap -import kotlin.io.path.* -import kotlin.reflect.KClass -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.jvm.isAccessible -import kotlin.system.measureTimeMillis - -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -annotation class Input - -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -annotation class Output - -interface SetupStep { - val name: String - - val hashFile: Path - - fun run(context: SetupHandler.ExecutionContext) - - fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {} -} - -object StepExecutor { - private data class InputOutputData( - val inputs: List>, - val outputs: List>, - ) - - private val inputOutputDataCache: MutableMap, InputOutputData> = - ConcurrentHashMap, InputOutputData>() - - fun executeSteps(expectingChange: Boolean, context: SetupHandler.ExecutionContext, vararg steps: SetupStep) { - // if we aren't expecting change, assume the last step is the output that matters - // and only verify its inputs/outputs - if it fails then we need to go back through - // and check each step anyway - if (!expectingChange) { - val lastStep = steps.last() - if (makeHashFunction(lastStep).upToDate(lastStep.hashFile)) { - return - } - } - - try { - for (step in steps) { - executeStep(context, step) - } - } catch (ex: Exception) { - for (step in steps) { - step.hashFile.deleteIfExists() - } - throw PaperweightException("Failed to execute steps, invalidated relevant caches. Run with --stacktrace for more details.", ex) - } - } - - fun executeStep(context: SetupHandler.ExecutionContext, step: SetupStep) { - val hashFunction = makeHashFunction(step) - - if (hashFunction.upToDate(step.hashFile)) { - return - } - - UserdevSetup.LOGGER.lifecycle(":executing '{}'", step.name) - val elapsed = measureTimeMillis { - step.run(context) - } - UserdevSetup.LOGGER.info("done executing '{}', took {}s", step.name, elapsed / 1000.00) - - hashFunction.writeHash(step.hashFile) - } - - private fun makeHashFunction(step: SetupStep): HashFunction { - val data = step.inputOutputData - val inputs = data.inputs.mapNotNull { it.get(step) } - val outputs = data.outputs.mapNotNull { it.get(step) } - - return buildHashFunction(inputs, outputs) { - step.touchHashFunctionBuilder(this) - } - } - - private val SetupStep.inputOutputData: InputOutputData - get() = inputOutputDataCache.computeIfAbsent(this::class) { - InputOutputData( - it.collectAnnotatedDeclaredProperties(), - it.collectAnnotatedDeclaredProperties(), - ) - } - - private inline fun KClass<*>.collectAnnotatedDeclaredProperties() = - collectDeclaredProperties { - it.annotations.any { a -> a is A } - } - - @Suppress("unchecked_cast") - private fun KClass<*>.collectDeclaredProperties( - filter: (KProperty1<*, *>) -> Boolean - ): List> = - declaredMemberProperties.filter(filter).map { - it.isAccessible = true - it as KProperty1 - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/util/utils.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/util/utils.kt deleted file mode 100644 index 7069e87c4..000000000 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/util/utils.kt +++ /dev/null @@ -1,236 +0,0 @@ -/* - * paperweight is a Gradle plugin for the PaperMC project. - * - * Copyright (c) 2023 Kyle Wood (DenWav) - * 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.internal.setup.util - -import io.papermc.paperweight.DownloadService -import io.papermc.paperweight.userdev.PaperweightUser -import io.papermc.paperweight.userdev.internal.setup.UserdevSetup -import io.papermc.paperweight.util.* -import io.papermc.paperweight.util.constants.* -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.time.Duration -import java.util.stream.Collectors -import kotlin.io.path.* -import kotlin.streams.asSequence -import kotlin.system.measureTimeMillis -import org.gradle.api.Project -import org.gradle.api.provider.Provider - -val paperweightHash: String by lazy { hashPaperweightJar() } - -fun Path.siblingLogFile(): Path = withDifferentExtension("log") - -fun Path.siblingHashesFile(): Path = withDifferentExtension("hashes") - -fun Path.siblingLogAndHashesFiles() = Pair(siblingLogFile(), siblingHashesFile()) - -fun hash(vararg things: Any): String = hash(things.toList()) - -fun hash(things: List): String { - val strings = arrayListOf() - val paths = arrayListOf() - - for (thing in things) { - when (thing) { - is String -> strings.add(thing) - is Path -> paths.add(thing) - is Iterable<*> -> strings.add(hash(thing.filterNotNull())) - else -> error("Unknown type: ${thing.javaClass.name}") - } - } - - return hashFiles(paths) + if (strings.isEmpty()) { - "" - } else { - "\n" + strings.sorted().joinToString("\n") { it.hash(HashingAlgorithm.SHA256).asHexString() } - } -} - -fun hashFiles(files: List): String = files.asSequence() - .filter { it.isRegularFile() } - .sortedBy { it.pathString } - .joinToString("\n") { - "${it.fileName.pathString}:${it.sha256asHex()}" - } - -fun hashDirectory(dir: Path): String = Files.walk(dir).use { stream -> hashFiles(stream.filter { it.isRegularFile() }.collect(Collectors.toList())) } - -fun DownloadService.download( - downloadName: String, - remote: String, - destination: Path, - forceDownload: Boolean = false, - expectedHash: Hash? = null -): DownloadResult { - val hashFile = destination.siblingHashesFile() - - val toHash = mutableListOf(remote, destination) - expectedHash?.let { toHash += it.valueLower } - val upToDate = !forceDownload && - hashFile.isRegularFile() && - hashFile.readText() == hash(toHash) - if (upToDate) { - return DownloadResult(destination, false, Unit) - } - - UserdevSetup.LOGGER.lifecycle(":executing 'download {}'", downloadName) - val elapsed = measureTimeMillis { - destination.parent.createDirectories() - destination.deleteIfExists() - download(remote, destination, expectedHash) - } - UserdevSetup.LOGGER.info("done executing 'download {}', took {}s", downloadName, elapsed / 1000.00) - hashFile.writeText(hash(toHash)) - - return DownloadResult(destination, true, Unit) -} - -fun buildHashFunction(vararg things: Any, op: HashFunctionBuilder.() -> Unit = {}): HashFunction = HashFunction { - val builder = HashFunctionBuilder.create() - builder.op() - if (builder.includePaperweightHash) { - builder.include(paperweightHash) - } - builder.include(*things) - - hash(builder) -} - -data class DownloadResult(val path: Path, val didDownload: Boolean, val data: D) { - fun mapData(mapper: (DownloadResult) -> N): DownloadResult = DownloadResult(path, didDownload, mapper(this)) -} - -interface HashFunctionBuilder : MutableList { - var includePaperweightHash: Boolean - - fun include(vararg things: Any): Boolean = addAll(things.toList()) - - fun include(thing: Any): Boolean = add(thing) - - companion object { - fun create(): HashFunctionBuilder = HashFunctionBuilderImpl() - } - - private class HashFunctionBuilderImpl( - override var includePaperweightHash: Boolean = true, - ) : HashFunctionBuilder, MutableList by ArrayList() -} - -fun interface HashFunction : () -> String { - fun writeHash(hashFile: Path) { - hashFile.parent.createDirectories() - hashFile.writeText(this()) - } - - fun upToDate(hashFile: Path): Boolean { - return hashFile.isRegularFile() && hashFile.readText() == this() - } -} - -private fun hashPaperweightJar(): String { - val userdevShadowJar = Paths.get(PaperweightUser::class.java.protectionDomain.codeSource.location.toURI()) - return userdevShadowJar.sha256asHex() -} - -fun lockSetup(cache: Path, canBeNested: Boolean = false, action: () -> R): R { - val lockFile = cache.resolve(USERDEV_SETUP_LOCK) - val alreadyHad = acquireProcessLockWaiting(lockFile) - try { - return action() - } finally { - if (!canBeNested || !alreadyHad) { - lockFile.deleteForcefully() - } - } -} - -// set by most CI -val Project.ci: Provider - get() = providers.environmentVariable("CI") - .map { it.toBoolean() } - .orElse(false) - -private fun stableProp(name: String) = "paperweight.$name" - -private fun experimentalProp(name: String) = "paperweight.experimental.$name" - -val Project.genSources: Boolean - get() { - val ci = ci.get() - val prop = providers.gradleProperty(experimentalProp("genSources")).orNull?.toBoolean() - return prop ?: !ci - } - -val Project.sharedCaches: Boolean - get() = providers.gradleProperty(stableProp("sharedCaches")) - .map { it.toBoolean() } - .orElse(true) - .get() - -private fun deleteUnusedAfter(target: Project): Long = target.providers.gradleProperty(stableProp("sharedCaches.deleteUnusedAfter")) - .map { value -> parseDuration(value) } - .orElse(Duration.ofDays(7)) - .map { duration -> duration.toMillis() } - .get() - -fun lastUsedFile(cacheDir: Path): Path = cacheDir.resolve(paperSetupOutput("last-used", "txt")) - -fun cleanSharedCaches(target: Project, root: Path) { - if (!root.exists()) { - return - } - val toDelete = Files.walk(root).use { stream -> - stream.asSequence() - .filter { it.name == "last-used.txt" } - .mapNotNull { - val pwDir = it.parent.parent // paperweight dir - val cacheDir = pwDir.parent // cache dir - val lock = cacheDir.resolve(USERDEV_SETUP_LOCK) - if (lock.exists() && ProcessHandle.of(lock.readText().toLong()).isPresent) { - return@mapNotNull null - } - val lastUsed = it.readText().toLong() - val since = System.currentTimeMillis() - lastUsed - val cutoff = deleteUnusedAfter(target) - if (since > cutoff) pwDir else null - } - .toList() - } - for (path in toDelete) { - path.deleteRecursive() - - // clean up empty parent directories - var parent: Path = path.parent - while (true) { - val entries = parent.listDirectoryEntries() - if (entries.isEmpty()) { - parent.deleteIfExists() - } else { - break - } - parent = parent.parent - } - } -} diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt index 050a7a1e8..eca2caf9f 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt @@ -23,13 +23,14 @@ package io.papermc.paperweight.userdev.internal.setup.v2 import io.papermc.paperweight.tasks.* -import io.papermc.paperweight.userdev.internal.setup.ExtractedBundle -import io.papermc.paperweight.userdev.internal.setup.RunPaperclip +import io.papermc.paperweight.userdev.internal.action.* +import io.papermc.paperweight.userdev.internal.action.Input +import io.papermc.paperweight.userdev.internal.action.Output +import io.papermc.paperweight.userdev.internal.setup.BundleInfo import io.papermc.paperweight.userdev.internal.setup.SetupHandler import io.papermc.paperweight.userdev.internal.setup.UserdevSetup import io.papermc.paperweight.userdev.internal.setup.UserdevSetupTask -import io.papermc.paperweight.userdev.internal.setup.step.* -import io.papermc.paperweight.userdev.internal.setup.util.* +import io.papermc.paperweight.userdev.internal.setup.action.* import io.papermc.paperweight.util.* import io.papermc.paperweight.util.constants.* import java.nio.file.Path @@ -40,154 +41,178 @@ import org.gradle.kotlin.dsl.* class SetupHandlerImplV2( private val parameters: UserdevSetup.Parameters, - private val bundle: ExtractedBundle, - private val cache: Path = parameters.cache.path, + private val bundle: BundleInfo, ) : SetupHandler { - private val vanillaSteps by lazy { - VanillaSteps( - bundle.config.minecraftVersion, - cache, - parameters.downloadService.get(), - bundle.changed, + private fun createDispatcher(context: SetupHandler.ExecutionContext): WorkDispatcher { + val dispatcher = WorkDispatcher.create(parameters.cache.path) + dispatcher.overrideTerminalInputHash(parameters.bundleZipHash.get()) + + val javaLauncher = javaLauncherValue(context.javaLauncher) + val mcVer = StringValue(bundle.config.minecraftVersion) + val bundleZip = fileValue(bundle.zip) + dispatcher.provided( + javaLauncher, + mcVer, + bundleZip, ) - } - private val filteredVanillaServerJar: Path = cache.resolve(paperSetupOutput("filterJar", "jar")) - private val minecraftLibraryJars = cache.resolve(MINECRAFT_JARS_PATH) - private val mojangPlusYarnMappings: Path = cache.resolve(MOJANG_YARN_MAPPINGS) - private val mappedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("mappedMinecraftServerJar", "jar")) - private val fixedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("fixedMinecraftServerJar", "jar")) - private val accessTransformedServerJar: Path = cache.resolve(paperSetupOutput("accessTransformedServerJar", "jar")) - private val decompiledMinecraftServerJar: Path = cache.resolve(paperSetupOutput("decompileMinecraftServerJar", "jar")) - private val patchedSourcesJar: Path = cache.resolve(paperSetupOutput("patchedSourcesJar", "jar")) - private val mojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("applyMojangMappedPaperclipPatch", "jar")) - - private fun minecraftLibraryJars(): List = minecraftLibraryJars.listDirectoryEntries("*.jar") - - private fun generateSources(context: SetupHandler.ExecutionContext) { - vanillaSteps.downloadVanillaServerJar() - val downloadMcLibs = object : SetupStep { - override val name: String = "download minecraft libraries" - - override val hashFile: Path = cache.resolve(paperSetupOutput("minecraftLibraries", "hashes")) + val vanillaDownloads = dispatcher.register( + "vanillaServerDownloads", + VanillaServerDownloads( + mcVer, + dispatcher.outputFile("vanillaServer.jar"), + dispatcher.outputFile("mojangServerMappings.txt"), + parameters.downloadService.get(), + ) + ) - override fun run(context: SetupHandler.ExecutionContext) { + class DownloadMcLibs( + @Output + val minecraftLibraryJars: DirectoryValue, + @Input + val vanillaServerLibraries: ListValue, + ) : WorkDispatcher.Action { + override fun execute() { downloadLibraries( download = parameters.downloadService, workerExecutor = context.workerExecutor, - targetDir = minecraftLibraryJars, + targetDir = minecraftLibraryJars.get(), repositories = listOf(MC_LIBRARY_URL, MAVEN_CENTRAL_URL), - libraries = bundle.config.buildData.vanillaServerLibraries, + libraries = vanillaServerLibraries.get(), sources = false ).await() } - - override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) { - builder.include(MC_LIBRARY_URL) - builder.include(bundle.config.buildData.vanillaServerLibraries) - builder.include(minecraftLibraryJars()) - builder.includePaperweightHash = false - } } - val filterVanillaJarStep = FilterVanillaJar(vanillaSteps.mojangJar, bundle.config.buildData.vanillaJarIncludes, filteredVanillaServerJar) - - val genMappingsStep = GenerateMappingsStep.create( - context, - vanillaSteps, - filteredVanillaServerJar, - ::minecraftLibraryJars, - mojangPlusYarnMappings, + val downloadMcLibs = dispatcher.register( + "downloadMinecraftLibraries", + DownloadMcLibs( + dispatcher.outputDir("output"), + stringListValue(bundle.config.buildData.vanillaServerLibraries), + ) ) - - val remapMinecraftStep = RemapMinecraft.create( - context, - bundle.config.remap.args, - filteredVanillaServerJar, - ::minecraftLibraryJars, - mojangPlusYarnMappings, - mappedMinecraftServerJar, - cache, + dispatcher.provided(downloadMcLibs.vanillaServerLibraries) + + val filterVanillaJar = dispatcher.register( + "filterVanillaJar", + FilterVanillaJarAction( + vanillaDownloads.serverJar, + stringListValue(bundle.config.buildData.vanillaJarIncludes), + dispatcher.outputFile("output.jar"), + ) ) - - val fixStep = FixMinecraftJar( - mappedMinecraftServerJar, - fixedMinecraftServerJar, - vanillaSteps.mojangJar, - true, + dispatcher.provided(filterVanillaJar.includes) + + val generateMappings = dispatcher.register( + "generateMappings", + GenerateMappingsAction( + javaLauncher, + context.workerExecutor, + vanillaDownloads.serverMappings, + filterVanillaJar.outputJar, + FileCollectionValue(context.paramMappingsConfig), + downloadMcLibs.minecraftLibraryJars, + dispatcher.outputFile("output.tiny"), + ) ) - - val atStep = AccessTransformMinecraft( - bundle.dir.resolve(bundle.config.buildData.accessTransformFile), - fixedMinecraftServerJar, - accessTransformedServerJar, + dispatcher.provided(generateMappings.paramMappings) + + val remap = dispatcher.register( + "remapMinecraft", + RemapMinecraftAction( + javaLauncher, + stringListValue(bundle.config.remap.args), + filterVanillaJar.outputJar, + downloadMcLibs.minecraftLibraryJars, + generateMappings.outputMappings, + FileCollectionValue(context.remapperConfig), + dispatcher.outputFile("output.jar"), + ) ) - - val decomp = DecompileMinecraft.create( - context, - accessTransformedServerJar, - decompiledMinecraftServerJar, - cache, - ::minecraftLibraryJars, - bundle.config.decompile.args, + dispatcher.provided(remap.minecraftRemapArgs) + dispatcher.provided(remap.remapper) + + val fix = dispatcher.register( + "fixMinecraftJar", + FixMinecraftJarAction( + javaLauncher, + context.workerExecutor, + remap.outputJar, + dispatcher.outputFile("output.jar"), + vanillaDownloads.serverJar, + true, + ) ) - val applyDevBundlePatchesStep = ApplyDevBundlePatches( - decompiledMinecraftServerJar, - bundle.dir.resolve(bundle.config.patchDir), - patchedSourcesJar + val at = dispatcher.register( + "accessTransformMinecraft", + AccessTransformMinecraftAction( + javaLauncher, + context.workerExecutor, + bundleZip, + StringValue(bundle.config.buildData.accessTransformFile), + fix.outputJar, + dispatcher.outputFile("output.jar"), + ) ) - - StepExecutor.executeSteps( - bundle.changed, - context, - downloadMcLibs, - filterVanillaJarStep, - genMappingsStep, - remapMinecraftStep, - fixStep, - atStep, - decomp, - applyDevBundlePatchesStep, + dispatcher.provided(at.atPath) + + val decompile = dispatcher.register( + "decompileMinecraftServer", + DecompileMinecraftAction( + javaLauncher, + at.outputJar, + dispatcher.outputFile("output.jar"), + downloadMcLibs.minecraftLibraryJars, + stringListValue(bundle.config.decompile.args), + FileCollectionValue(context.decompilerConfig), + ) + ) + dispatcher.provided( + decompile.decompileArgs, + decompile.decompiler, ) - applyMojangMappedPaperclipPatch(context) - - StepExecutor.executeStep( - context, - FilterPaperShadowJar( - patchedSourcesJar, - mojangMappedPaperJar, - filteredMojangMappedPaperJar, - bundle.config.buildData.relocations, + val applyPatches = dispatcher.register( + "applyDevBundlePatches", + ApplyDevBundlePatchesAction( + decompile.outputJar, + bundleZip, + StringValue(bundle.config.patchDir), + dispatcher.outputFile("output.jar"), ) ) - } - - // This can be called when a user queries the server jar provider in - // PaperweightUserExtension, possibly by a task running in a separate - // thread to dependency resolution. - @Synchronized - private fun applyMojangMappedPaperclipPatch(context: SetupHandler.ExecutionContext) { - if (setupCompleted) { - return - } - - lockSetup(cache, true) { - StepExecutor.executeStep( - context, - RunPaperclip( - bundle.dir.resolve(bundle.config.buildData.mojangMappedPaperclipFile), - mojangMappedPaperJar, - vanillaSteps.mojangJar, - minecraftVersion, - false, - ) + dispatcher.provided(applyPatches.patchesPath) + + val applyPaperclip = dispatcher.register( + "applyPaperclipPatch", + RunPaperclipAction( + javaLauncher, + bundleZip, + StringValue(bundle.config.buildData.mojangMappedPaperclipFile), + dispatcher.outputFile("output.jar"), + vanillaDownloads.serverJar, + mcVer, + false, ) - } - } + ) + dispatcher.provided(applyPaperclip.paperclipPath) + + val filterPaperShadowJar = dispatcher.register( + "filterPaperShadowJar", + FilterPaperShadowJarAction( + applyPatches.outputJar, + applyPaperclip.outputJar, + dispatcher.outputFile("output.jar"), + value(bundle.config.buildData.relocations) { + listOf(InputStreamProvider.wrap(gson.toJson(it).byteInputStream())) + }, + ) + ) + dispatcher.provided(filterPaperShadowJar.relocations) - private val filteredMojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("filteredMojangMappedPaperJar", "jar")) + return dispatcher + } override fun populateCompileConfiguration(context: SetupHandler.ConfigurationContext, dependencySet: DependencySet) { dependencySet.add(context.dependencyFactory.create(context.layout.files(context.setupTask.flatMap { it.mappedServerJar }))) @@ -206,34 +231,44 @@ class SetupHandlerImplV2( dependencySet.add(context.dependencyFactory.create(context.layout.files(context.setupTask.flatMap { it.legacyPaperclipResult }))) } - private var setupCompleted = false + @Volatile + private var completedOutput: Pair? = null @Synchronized - override fun combinedOrClassesJar(context: SetupHandler.ExecutionContext): Path { - if (setupCompleted) { - return filteredMojangMappedPaperJar + override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + if (completedOutput != null) { + val (main, legacy) = requireNotNull(completedOutput) + main.copyTo(output, true) + legacy.copyTo(requireNotNull(legacyOutput), true) + return } - lockSetup(cache) { - generateSources(context) + val dispatcher = createDispatcher(context) + val filter = dispatcher.registered("filterPaperShadowJar").outputJar + val paperclip = dispatcher.registered("applyPaperclipPatch").outputJar + context.withProgressLogger { progressLogger -> + dispatcher.dispatch(filter, paperclip) { + progressLogger.progress(it) + } } + filter.get().copyTo(output, true) + paperclip.get().copyTo(requireNotNull(legacyOutput), true) + completedOutput = filter.get() to paperclip.get() + } - setupCompleted = true - - return filteredMojangMappedPaperJar + override fun extractReobfMappings(output: Path) { + bundle.zip.openZipSafe().use { fs -> + fs.getPath(bundle.config.buildData.reobfMappingsFile).copyTo(output, true) + } } override fun afterEvaluate(project: Project) { super.afterEvaluate(project) project.tasks.withType(UserdevSetupTask::class).configureEach { - mappedServerJar.set(filteredMojangMappedPaperJar) - legacyPaperclipResult.set(mojangMappedPaperJar) + legacyPaperclipResult.set(layout.cache.resolve(paperTaskOutput("legacyPaperclipResult", "jar"))) } } - override val reobfMappings: Path - get() = bundle.dir.resolve(bundle.config.buildData.reobfMappingsFile) - override val minecraftVersion: String get() = bundle.config.minecraftVersion diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt index 5b664a06d..6f00ee04b 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt @@ -22,130 +22,162 @@ package io.papermc.paperweight.userdev.internal.setup.v5 -import io.papermc.paperweight.userdev.internal.setup.ExtractedBundle -import io.papermc.paperweight.userdev.internal.setup.RunPaperclip +import io.papermc.paperweight.userdev.internal.action.FileCollectionValue +import io.papermc.paperweight.userdev.internal.action.StringValue +import io.papermc.paperweight.userdev.internal.action.WorkDispatcher +import io.papermc.paperweight.userdev.internal.action.fileValue +import io.papermc.paperweight.userdev.internal.action.javaLauncherValue +import io.papermc.paperweight.userdev.internal.action.stringListValue +import io.papermc.paperweight.userdev.internal.setup.BundleInfo import io.papermc.paperweight.userdev.internal.setup.SetupHandler import io.papermc.paperweight.userdev.internal.setup.UserdevSetup -import io.papermc.paperweight.userdev.internal.setup.UserdevSetupTask -import io.papermc.paperweight.userdev.internal.setup.step.* -import io.papermc.paperweight.userdev.internal.setup.util.* +import io.papermc.paperweight.userdev.internal.setup.action.* import io.papermc.paperweight.util.* -import io.papermc.paperweight.util.constants.* import java.nio.file.Path -import org.gradle.api.Project +import kotlin.io.path.* import org.gradle.api.artifacts.DependencySet -import org.gradle.kotlin.dsl.* class SetupHandlerImplV5( private val parameters: UserdevSetup.Parameters, - private val bundle: ExtractedBundle, - private val cache: Path = parameters.cache.path, + private val bundle: BundleInfo, ) : SetupHandler { - private val vanillaSteps by lazy { - VanillaSteps( - bundle.config.minecraftVersion, - cache, - parameters.downloadService.get(), - bundle.changed, - ) - } - private val vanillaServerJar: Path = cache.resolve(paperSetupOutput("vanillaServerJar", "jar")) - private val minecraftLibraryJars = cache.resolve(MINECRAFT_JARS_PATH) - private val filteredVanillaServerJar: Path = cache.resolve(paperSetupOutput("filterJar", "jar")) - private val mojangPlusYarnMappings: Path = cache.resolve(MOJANG_YARN_MAPPINGS) - private val mappedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("mappedMinecraftServerJar", "jar")) - private val fixedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("fixedMinecraftServerJar", "jar")) - private val accessTransformedServerJar: Path = cache.resolve(paperSetupOutput("accessTransformedServerJar", "jar")) - private val decompiledMinecraftServerJar: Path = cache.resolve(paperSetupOutput("decompileMinecraftServerJar", "jar")) - private val patchedSourcesJar: Path = cache.resolve(paperSetupOutput("patchedSourcesJar", "jar")) - private val mojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("applyMojangMappedPaperclipPatch", "jar")) - - private fun minecraftLibraryJars(): List = minecraftLibraryJars.filesMatchingRecursive("*.jar") - - private fun generateSources(context: SetupHandler.ExecutionContext) { - vanillaSteps.downloadVanillaServerJar() - applyMojangMappedPaperclipPatch(context) - - val extractStep = createExtractFromBundlerStep() - - val filterVanillaJarStep = FilterVanillaJar(vanillaServerJar, bundle.config.buildData.vanillaJarIncludes, filteredVanillaServerJar) - - val genMappingsStep = GenerateMappingsStep.create( - context, - vanillaSteps, - filteredVanillaServerJar, - ::minecraftLibraryJars, - mojangPlusYarnMappings, + private fun createDispatcher(context: SetupHandler.ExecutionContext): WorkDispatcher { + val dispatcher = WorkDispatcher.create(parameters.cache.path) + dispatcher.overrideTerminalInputHash(parameters.bundleZipHash.get()) + + val javaLauncher = javaLauncherValue(context.javaLauncher) + val mcVer = StringValue(bundle.config.minecraftVersion) + val bundleZip = fileValue(bundle.zip) + dispatcher.provided( + javaLauncher, + mcVer, + bundleZip, ) - val remapMinecraftStep = RemapMinecraft.create( - context, - bundle.config.buildData.minecraftRemapArgs, - filteredVanillaServerJar, - ::minecraftLibraryJars, - mojangPlusYarnMappings, - mappedMinecraftServerJar, - cache, + val vanillaDownloads = dispatcher.register( + "vanillaServerDownloads", + VanillaServerDownloads( + mcVer, + dispatcher.outputFile("vanillaServer.jar"), + dispatcher.outputFile("mojangServerMappings.txt"), + parameters.downloadService.get(), + ) ) - val fixStep = FixMinecraftJar(mappedMinecraftServerJar, fixedMinecraftServerJar, vanillaServerJar) - - val atStep = AccessTransformMinecraft( - bundle.dir.resolve(bundle.config.buildData.accessTransformFile), - fixedMinecraftServerJar, - accessTransformedServerJar, + val applyPaperclip = dispatcher.register( + "applyPaperclipPatch", + RunPaperclipAction( + javaLauncher, + bundleZip, + StringValue(bundle.config.buildData.mojangMappedPaperclipFile), + dispatcher.outputFile("output.jar"), + vanillaDownloads.serverJar, + mcVer, + ) ) - - val decomp = DecompileMinecraft.create( - context, - accessTransformedServerJar, - decompiledMinecraftServerJar, - cache, - ::minecraftLibraryJars, - bundle.config.decompile.args, + dispatcher.provided(applyPaperclip.paperclipPath) + + val extract = dispatcher.register( + "extractFromBundler", + ExtractFromBundlerAction( + vanillaDownloads.serverJar, + dispatcher.outputFile("vanillaServer.jar"), + dispatcher.outputDir("minecraftLibraries"), + ) ) - val applyDevBundlePatchesStep = ApplyDevBundlePatches( - decompiledMinecraftServerJar, - bundle.dir.resolve(bundle.config.patchDir), - patchedSourcesJar, - mojangMappedPaperJar + val filterVanillaJar = dispatcher.register( + "filterVanillaJar", + FilterVanillaJarAction( + extract.vanillaServerJar, + stringListValue(bundle.config.buildData.vanillaJarIncludes), + dispatcher.outputFile("output.jar"), + ) ) - - StepExecutor.executeSteps( - bundle.changed, - context, - extractStep, - filterVanillaJarStep, - genMappingsStep, - remapMinecraftStep, - fixStep, - atStep, - decomp, - applyDevBundlePatchesStep, + dispatcher.provided(filterVanillaJar.includes) + + val generateMappings = dispatcher.register( + "generateMappings", + GenerateMappingsAction( + javaLauncher, + context.workerExecutor, + vanillaDownloads.serverMappings, + filterVanillaJar.outputJar, + FileCollectionValue(context.paramMappingsConfig), + extract.minecraftLibraryJars, + dispatcher.outputFile("output.tiny"), + ) + ) + dispatcher.provided(generateMappings.paramMappings) + + val remap = dispatcher.register( + "remapMinecraft", + RemapMinecraftAction( + javaLauncher, + stringListValue(bundle.config.buildData.minecraftRemapArgs), + filterVanillaJar.outputJar, + extract.minecraftLibraryJars, + generateMappings.outputMappings, + FileCollectionValue(context.remapperConfig), + dispatcher.outputFile("output.jar"), + ) + ) + dispatcher.provided(remap.minecraftRemapArgs) + dispatcher.provided(remap.remapper) + + val fix = dispatcher.register( + "fixMinecraftJar", + FixMinecraftJarAction( + javaLauncher, + context.workerExecutor, + remap.outputJar, + dispatcher.outputFile("output.jar"), + extract.vanillaServerJar, + ) ) - } - // This can be called when a user queries the server jar provider in - // PaperweightUserExtension, possibly by a task running in a separate - // thread to dependency resolution. - @Synchronized - private fun applyMojangMappedPaperclipPatch(context: SetupHandler.ExecutionContext) { - if (setupCompleted) { - return - } + val at = dispatcher.register( + "accessTransformMinecraft", + AccessTransformMinecraftAction( + javaLauncher, + context.workerExecutor, + bundleZip, + StringValue(bundle.config.buildData.accessTransformFile), + fix.outputJar, + dispatcher.outputFile("output.jar"), + ) + ) + dispatcher.provided(at.atPath) + + val decompile = dispatcher.register( + "decompileMinecraftServer", + DecompileMinecraftAction( + javaLauncher, + at.outputJar, + dispatcher.outputFile("output.jar"), + extract.minecraftLibraryJars, + stringListValue(bundle.config.decompile.args), + FileCollectionValue(context.decompilerConfig), + ) + ) + dispatcher.provided( + decompile.decompileArgs, + decompile.decompiler, + ) - lockSetup(cache, true) { - StepExecutor.executeStep( - context, - RunPaperclip( - bundle.dir.resolve(bundle.config.buildData.mojangMappedPaperclipFile), - mojangMappedPaperJar, - vanillaSteps.mojangJar, - minecraftVersion, - ) + val applyPatches = dispatcher.register( + "applyDevBundlePatches", + ApplyDevBundlePatchesAction( + decompile.outputJar, + bundleZip, + StringValue(bundle.config.patchDir), + dispatcher.outputFile("output.jar"), + applyPaperclip.outputJar, ) - } + ) + dispatcher.provided(applyPatches.patchesPath) + + return dispatcher } override fun populateCompileConfiguration(context: SetupHandler.ConfigurationContext, dependencySet: DependencySet) { @@ -180,49 +212,37 @@ class SetupHandlerImplV5( } } - private var setupCompleted = false + @Volatile + private var completedOutput: Path? = null @Synchronized - override fun combinedOrClassesJar(context: SetupHandler.ExecutionContext): Path { - if (setupCompleted) { - return if (parameters.genSources.get()) { - patchedSourcesJar - } else { - mojangMappedPaperJar - } + override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + if (completedOutput != null) { + requireNotNull(completedOutput).copyTo(output, true) + return } - val ret = lockSetup(cache) { - if (parameters.genSources.get()) { - generateSources(context) - patchedSourcesJar - } else { - vanillaSteps.downloadVanillaServerJar() - StepExecutor.executeStep(context, createExtractFromBundlerStep()) - applyMojangMappedPaperclipPatch(context) - mojangMappedPaperJar + val dispatcher = createDispatcher(context) + val request = if (parameters.genSources.get()) { + dispatcher.registered("applyDevBundlePatches").outputJar + } else { + dispatcher.registered("applyPaperclipPatch").outputJar + } + context.withProgressLogger { progressLogger -> + dispatcher.dispatch(request) { + progressLogger.progress(it) } } - - setupCompleted = true - - return ret + request.get().copyTo(output, true) + completedOutput = request.get() } - override fun afterEvaluate(project: Project) { - super.afterEvaluate(project) - project.tasks.withType(UserdevSetupTask::class).configureEach { - if (parameters.genSources.get()) { - mappedServerJar.set(patchedSourcesJar) - } else { - mappedServerJar.set(mojangMappedPaperJar) - } + override fun extractReobfMappings(output: Path) { + bundle.zip.openZipSafe().use { fs -> + fs.getPath(bundle.config.buildData.reobfMappingsFile).copyTo(output, true) } } - override val reobfMappings: Path - get() = bundle.dir.resolve(bundle.config.buildData.reobfMappingsFile) - override val minecraftVersion: String get() = bundle.config.minecraftVersion @@ -243,12 +263,4 @@ class SetupHandlerImplV5( override val libraryRepositories: List get() = bundle.config.buildData.libraryRepositories - - private fun createExtractFromBundlerStep(): ExtractFromBundlerStep = ExtractFromBundlerStep( - cache, - vanillaSteps, - vanillaServerJar, - minecraftLibraryJars, - ::minecraftLibraryJars - ) } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/util/utils.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/util/utils.kt new file mode 100644 index 000000000..54517cbf7 --- /dev/null +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/util/utils.kt @@ -0,0 +1,118 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.util + +import io.papermc.paperweight.util.* +import io.papermc.paperweight.util.constants.* +import java.nio.file.Files +import java.nio.file.Path +import java.time.Duration +import kotlin.io.path.* +import kotlin.streams.asSequence +import org.gradle.api.Project +import org.gradle.api.provider.Provider + +fun formatNs(ns: Long): String { + val ms = ns / 1_000_000 + if (ms < 1000) { + return "${ms}ms" + } + val s = ms / 1000 + val rem = ms % 1000 + if (s < 60) { + return "$s.${rem.toString().padStart(3, '0')}s" + } + val m = s / 60 + val remS = s % 60 + Math.round(rem / 1000.0) + return "${m}m ${remS}s" +} + +fun Path.siblingLogFile(): Path = withDifferentExtension("log") + +fun Path.jars(): List = filesMatchingRecursive("*.jar") + +// set by most CI +val Project.ci: Provider + get() = providers.environmentVariable("CI") + .map { it.toBoolean() } + .orElse(false) + +private fun stableProp(name: String) = "paperweight.$name" + +private fun experimentalProp(name: String) = "paperweight.experimental.$name" + +val Project.genSources: Boolean + get() { + val ci = ci.get() + val prop = providers.gradleProperty(experimentalProp("genSources")).orNull?.toBoolean() + return prop ?: !ci + } + +val Project.sharedCaches: Boolean + get() = providers.gradleProperty(stableProp("sharedCaches")) + .map { it.toBoolean() } + .orElse(true) + .get() + +fun deleteUnusedAfter(target: Project): Provider = target.providers.gradleProperty(stableProp("sharedCaches.deleteUnusedAfter")) + .map { value -> parseDuration(value) } + .orElse(Duration.ofDays(7)) + .map { duration -> duration.toMillis() } + +fun cleanSharedCaches(target: Project, root: Path) { + if (!root.exists()) { + return + } + val toDelete = Files.walk(root).use { stream -> + val cutoff: Long by lazy { deleteUnusedAfter(target).get() } + stream.asSequence() + .filter { it.name == "last-used.txt" } + .mapNotNull { + val pwDir = it.parent.parent // paperweight dir + val cacheDir = pwDir.parent // cache dir + val lock = cacheDir.resolve(USERDEV_SETUP_LOCK) + if (lock.exists() && ProcessHandle.of(lock.readText().toLong()).isPresent) { + return@mapNotNull null + } + val lastUsed = it.readText().toLong() + val since = System.currentTimeMillis() - lastUsed + if (since > cutoff) pwDir else null + } + .toList() + } + for (path in toDelete) { + path.deleteRecursive() + + // clean up empty parent directories + var parent: Path = path.parent + while (true) { + val entries = parent.listDirectoryEntries() + if (entries.isEmpty()) { + parent.deleteIfExists() + } else { + break + } + parent = parent.parent + } + } +} diff --git a/paperweight-userdev/src/test/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherTest.kt b/paperweight-userdev/src/test/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherTest.kt new file mode 100644 index 000000000..c5935f5e6 --- /dev/null +++ b/paperweight-userdev/src/test/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherTest.kt @@ -0,0 +1,137 @@ +/* + * paperweight is a Gradle plugin for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * 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.internal.action + +import io.papermc.paperweight.util.* +import java.nio.file.Path +import kotlin.io.path.* +import kotlin.test.Test +import kotlin.test.assertEquals +import org.junit.jupiter.api.io.TempDir + +class WorkDispatcherTest { + @Test + fun testExecution(@TempDir work: Path) { + val dispatcher = WorkDispatcher.create(work) + + val executed = mutableListOf() + + val writeText = dispatcher.register( + "writeText", + WriteText( + StringValue("hello, world"), + dispatcher.outputFile("output.txt"), + ) { executed.add("writeText") } + ) + dispatcher.provided(writeText.initialData) + val doubleText = dispatcher.register( + "doubleText", + DoubleText( + writeText.outputFile, + dispatcher.outputFile("output.txt"), + ) { executed.add("doubleText") } + ) + val doubleText2 = dispatcher.register( + "doubleText2", + DoubleText( + writeText.outputFile, + dispatcher.outputFile("output.txt"), + ) { executed.add("doubleText2") } + ) + val combine = dispatcher.register( + "combine", + CombineText( + writeText.outputFile, + doubleText.outputFile, + dispatcher.outputFile("output.txt"), + ) { executed.add("combine") } + ) + + dispatcher.dispatch( + doubleText.outputFile, + doubleText2.outputFile, + combine.outputFile, + ) + + // Assert execution happened in the correct order + assertEquals(listOf("writeText", "doubleText", "doubleText2", "combine"), executed) + + executed.clear() + + dispatcher.dispatch( + doubleText.outputFile, + doubleText2.outputFile, + combine.outputFile, + ) + + // Assert no execution happened when up-to-date + assertEquals(listOf(), executed) + } + + class WriteText( + @Input + val initialData: StringValue, + @Output + val outputFile: FileValue, + private val extraAction: Runnable, + ) : WorkDispatcher.Action { + override fun execute() { + val initial = initialData.get() + outputFile.get().cleanFile().writeText(initial) + extraAction.run() + } + } + + class DoubleText( + @Input + val inputFile: FileValue, + @Output + val outputFile: FileValue, + private val extraAction: Runnable, + ) : WorkDispatcher.Action { + override fun execute() { + val input = inputFile.get().readText() + val output = "$input\n$input" + outputFile.get().cleanFile().writeText(output) + extraAction.run() + } + } + + class CombineText( + @Input + val inputFile: FileValue, + @Input + val inputFile2: FileValue, + @Output + val outputFile: FileValue, + private val extraAction: Runnable, + ) : WorkDispatcher.Action { + override fun execute() { + val input = inputFile.get().readText() + val input2 = inputFile2.get().readText() + val output = "$input\n$input2" + outputFile.get().cleanFile().writeText(output) + extraAction.run() + } + } +} From dd770f979ccb5dbd51e28ed39d775e489573935d Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:24:04 -0800 Subject: [PATCH 02/10] Apply Java plugin automatically when using userdev --- .../kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt index 6428c8caa..8f7c0173d 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt @@ -70,6 +70,8 @@ abstract class PaperweightUser : Plugin { abstract val javaToolchainService: JavaToolchainService override fun apply(target: Project) { + target.plugins.apply("java") + val sharedCacheRoot = target.gradle.gradleUserHomeDir.toPath().resolve("caches/paperweight-userdev") target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) { From 63321ad53e4bfe6ff04b73abcf9e92343c93ba7d Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 16:46:15 -0800 Subject: [PATCH 03/10] Add shared parent for fs output types --- .../internal/action/WorkDispatcherImpl.kt | 4 ++-- .../userdev/internal/action/WorkGraph.kt | 8 +++---- .../userdev/internal/action/values.kt | 24 ++++++++----------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt index a4bdbc82b..bc4236276 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkDispatcherImpl.kt @@ -49,9 +49,9 @@ class WorkDispatcherImpl(private val work: Path) : WorkDispatcher { val outputs: List>, ) - override fun outputFile(name: String): FileValue = LazyFileValue(name) + override fun outputFile(name: String): FileValue = FileOutputValue(name) - override fun outputDir(name: String): DirectoryValue = LazyDirectoryValue(name) + override fun outputDir(name: String): DirectoryValue = DirectoryOutputValue(name) override fun provided(value: Value<*>) { if (registrations.any { it.outputs.contains(value) }) { diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt index 5266fa5cb..772b4ed5f 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/WorkGraph.kt @@ -80,9 +80,8 @@ class WorkGraph( val node = nodeCache[producer] ?: Node(producer, buildGraph(producer.inputs, nodeCache)).also { // This is only used as debug information it.registration.outputs.forEach { output -> - when (output) { - is LazyFileValue -> output.owner = it.registration.name - is LazyDirectoryValue -> output.owner = it.registration.name + if (output is FileSystemLocationOutputValue) { + output.owner = it.registration.name } } } @@ -205,8 +204,7 @@ class WorkGraph( private fun realizeOutputPaths(node: Node, work: Path, inputHash: String) { for (out in node.registration.outputs) { when (out) { - is LazyFileValue -> out.path = work.resolve("${node.registration.name}_$inputHash/${out.name}") - is LazyDirectoryValue -> out.path = work.resolve("${node.registration.name}_$inputHash/${out.name}") + is FileSystemLocationOutputValue -> out.path = work.resolve("${node.registration.name}_$inputHash/${out.name}") else -> throw PaperweightException("Unsupported output type ${out::class.java.name}") } } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt index 68e5e5246..60f8591f3 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/values.kt @@ -121,32 +121,28 @@ fun fileListValue(files: List): ListValue { class FileValueImpl(private val path: Path) : FileValue { override fun get(): Path = path - override fun toString(): String = "FileValue('$path')" + override fun toString(): String = "FileValueImpl('$path')" } class DirectoryValueImpl(private val path: Path) : DirectoryValue { override fun get(): Path = path - override fun toString(): String = "DirectoryValue('$path')" + override fun toString(): String = "DirectoryValueImpl('$path')" } -class LazyFileValue(val name: String) : FileValue { - var path: Path? = null - var owner: String? = null +abstract class FileSystemLocationOutputValue( + val name: String, + var path: Path? = null, + var owner: String? = null, +) : Value { + override fun toString(): String = "${javaClass.simpleName}(name='$name', owner='$owner')" override fun get(): Path = requireNotNull(path) { "Path is not yet populated" } - - override fun toString(): String = "LazyFileValue(name='$name', owner='$owner')" } -class LazyDirectoryValue(val name: String) : DirectoryValue { - var path: Path? = null - var owner: String? = null - - override fun get(): Path = requireNotNull(path) { "Path is not yet populated" } +class FileOutputValue(name: String) : FileSystemLocationOutputValue(name), FileValue - override fun toString(): String = "LazyDirectoryValue(name='$name', owner='$owner')" -} +class DirectoryOutputValue(name: String) : FileSystemLocationOutputValue(name), DirectoryValue fun javaLauncherValue(javaLauncher: JavaLauncher): Value = object : Value { override fun get(): JavaLauncher = javaLauncher From 3629d67a2f4bbeffa5fdac8d6e664f126e47f3fb Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:43:47 -0800 Subject: [PATCH 04/10] Use concurrent map for open file locks and retry when creating the lock file fails --- .../io/papermc/paperweight/util/file-lock.kt | 100 +++++++++--------- 1 file changed, 52 insertions(+), 48 deletions(-) diff --git a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt index 94da429f1..68e66a971 100644 --- a/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt +++ b/paperweight-lib/src/main/kotlin/io/papermc/paperweight/util/file-lock.kt @@ -24,13 +24,15 @@ package io.papermc.paperweight.util import io.papermc.paperweight.PaperweightException import java.nio.file.Path +import java.nio.file.StandardOpenOption +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock import kotlin.io.path.* import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging -private val openCurrentJvm: MutableMap = mutableMapOf() +private val openCurrentJvm: MutableMap = ConcurrentHashMap() fun withLock( lockFile: Path, @@ -45,9 +47,7 @@ fun withLock( while (true) { val normalized = lockFile.normalize().absolute() - val lock = synchronized(openCurrentJvm) { - openCurrentJvm.computeIfAbsent(normalized) { ReentrantLock() } - } + val lock = openCurrentJvm.computeIfAbsent(normalized) { ReentrantLock() } if (!lock.tryLock()) { if (firstFailedAcquire) { logger.lifecycle("Lock for '$lockFile' is currently held by another thread.") @@ -67,15 +67,8 @@ fun withLock( ) } } - val cont = synchronized(openCurrentJvm) { - if (openCurrentJvm[normalized] !== lock) { - lock.unlock() - true - } else { - false - } - } - if (cont) { + if (openCurrentJvm[normalized] !== lock) { + lock.unlock() continue } @@ -87,57 +80,68 @@ fun withLock( lockFile.deleteForcefully() } } finally { - synchronized(openCurrentJvm) { - lock.unlock() - openCurrentJvm.remove(normalized) - } + openCurrentJvm.remove(normalized) + lock.unlock() } } } -// TODO: Open an actual exclusive lock using FileChannel private fun acquireProcessLockWaiting( lockFile: Path, logger: Logger, - alreadyWaited: Long = 0, + alreadyWaited: Long, printInfoAfter: Long, timeoutMs: Long, ) { + if (!lockFile.parent.exists()) { + lockFile.parent.createDirectories() + } + val currentPid = ProcessHandle.current().pid() + var sleptMs: Long = alreadyWaited - if (lockFile.exists()) { - val lockingProcessId = lockFile.readText().toLong() - if (lockingProcessId == currentPid) { - throw IllegalStateException("Lock file '$lockFile' is currently held by this process.") - } else { - logger.lifecycle("Lock file '$lockFile' is currently held by pid '$lockingProcessId'.") - } + while (true) { + if (lockFile.exists()) { + val lockingProcessId = lockFile.readText().toLong() + if (lockingProcessId == currentPid) { + throw IllegalStateException("Lock file '$lockFile' is currently held by this process.") + } else { + logger.lifecycle("Lock file '$lockFile' is currently held by pid '$lockingProcessId'.") + } - if (ProcessHandle.of(lockingProcessId).isEmpty) { - logger.lifecycle("Locking process does not exist, assuming abrupt termination and deleting lock file.") - lockFile.deleteIfExists() - } else { - logger.lifecycle("Waiting for lock to be released...") - var sleptMs: Long = alreadyWaited - while (lockFile.exists()) { - Thread.sleep(100) - sleptMs += 100 - if (sleptMs >= printInfoAfter && sleptMs % printInfoAfter == 0L) { - logger.lifecycle( - "Have been waiting on lock file '$lockFile' held by pid '$lockingProcessId' for ${sleptMs / 1000 / 60} minute(s).\n" + - "If this persists for an unreasonable length of time, kill this process, run './gradlew --stop' and then try again.\n" + - "If the problem persists, the lock file may need to be deleted manually." - ) - } - if (sleptMs >= timeoutMs) { - throw PaperweightException("Have been waiting on lock file '$lockFile' for $sleptMs ms. Giving up as timeout is $timeoutMs ms.") + if (ProcessHandle.of(lockingProcessId).isEmpty) { + logger.lifecycle("Locking process does not exist, assuming abrupt termination and deleting lock file.") + lockFile.deleteIfExists() + } else { + logger.lifecycle("Waiting for lock to be released...") + while (lockFile.exists()) { + Thread.sleep(100) + sleptMs += 100 + if (sleptMs >= printInfoAfter && sleptMs % printInfoAfter == 0L) { + logger.lifecycle( + "Have been waiting on lock file '$lockFile' held by pid '$lockingProcessId' for ${sleptMs / 1000 / 60} minute(s).\n" + + "If this persists for an unreasonable length of time, kill this process, run './gradlew --stop', and then" + + " try again.\nIf the problem persists, the lock file may need to be deleted manually." + ) + } + if (sleptMs >= timeoutMs) { + throw PaperweightException( + "Have been waiting on lock file '$lockFile' for $sleptMs ms. Giving up as timeout is $timeoutMs ms." + ) + } } } } - } - if (!lockFile.parent.exists()) { - lockFile.parent.createDirectories() + try { + lockFile.writeText( + currentPid.toString(), + options = arrayOf(StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, StandardOpenOption.SYNC) + ) + } catch (e: FileAlreadyExistsException) { + continue + } + + break } - lockFile.writeText(currentPid.toString()) } From c881ac3d090f2a32389ab7ce9f4cd7cd9faf6214 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 17:49:57 -0800 Subject: [PATCH 05/10] Remove unused import --- .../paperweight/userdev/internal/setup/UserdevSetupTask.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt index de26c4c4a..9449c9fcb 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt @@ -27,7 +27,6 @@ import io.papermc.paperweight.userdev.internal.util.formatNs import io.papermc.paperweight.util.* import io.papermc.paperweight.util.constants.* import javax.inject.Inject -import kotlin.io.path.* import kotlin.system.measureNanoTime import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty From 72a1cbabb071d8662e60526b16348bf7140df33d Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 19:58:17 -0800 Subject: [PATCH 06/10] Time setup and file copy separately --- .../userdev/internal/setup/SetupHandler.kt | 7 ++++++- .../userdev/internal/setup/SetupHandlerImpl.kt | 11 +++++------ .../userdev/internal/setup/UserdevSetup.kt | 4 ++-- .../userdev/internal/setup/UserdevSetupTask.kt | 18 +++++++++++------- .../internal/setup/v2/SetupHandlerImplV2.kt | 14 +++++--------- .../internal/setup/v5/SetupHandlerImplV5.kt | 11 +++++------ 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt index b38933ceb..884d43486 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandler.kt @@ -48,7 +48,12 @@ interface SetupHandler { fun populateRuntimeConfiguration(context: ConfigurationContext, dependencySet: DependencySet) - fun generateCombinedOrClassesJar(context: ExecutionContext, output: Path, legacyOutput: Path?) + data class ArtifactsResult( + val mainOutput: Path, + val legacyOutput: Path?, + ) + + fun generateArtifacts(context: ExecutionContext): ArtifactsResult fun extractReobfMappings(output: Path) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt index d367b5d5d..860ba6578 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/SetupHandlerImpl.kt @@ -162,13 +162,12 @@ class SetupHandlerImpl( } @Volatile - private var completedOutput: Path? = null + private var completedOutput: SetupHandler.ArtifactsResult? = null @Synchronized - override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + override fun generateArtifacts(context: SetupHandler.ExecutionContext): SetupHandler.ArtifactsResult { if (completedOutput != null) { - requireNotNull(completedOutput).copyTo(output, true) - return + return requireNotNull(completedOutput) } // If the config cache is reused then the mache config may not be populated @@ -185,8 +184,8 @@ class SetupHandlerImpl( progressLogger.progress(it) } } - request.get().copyTo(output, true) - completedOutput = request.get() + return SetupHandler.ArtifactsResult(request.get(), null) + .also { completedOutput = it } } override fun extractReobfMappings(output: Path) { diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt index 6d444a83b..29e24b0eb 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetup.kt @@ -72,8 +72,8 @@ abstract class UserdevSetup : BuildService, SetupHandle setup.populateRuntimeConfiguration(context, dependencySet) } - override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { - setup.generateCombinedOrClassesJar(context, output, legacyOutput) + override fun generateArtifacts(context: SetupHandler.ExecutionContext): SetupHandler.ArtifactsResult { + return setup.generateArtifacts(context) } override fun extractReobfMappings(output: Path) { diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt index 9449c9fcb..0eb08e5f0 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/UserdevSetupTask.kt @@ -27,6 +27,7 @@ import io.papermc.paperweight.userdev.internal.util.formatNs import io.papermc.paperweight.util.* import io.papermc.paperweight.util.constants.* import javax.inject.Inject +import kotlin.io.path.* import kotlin.system.measureNanoTime import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty @@ -120,14 +121,17 @@ abstract class UserdevSetupTask : JavaLauncherTask() { macheCodebookConfig, ) - val took = measureNanoTime { - setupService.get().generateCombinedOrClassesJar( - context, - mappedServerJar.path.createParentDirectories(), - legacyPaperclipResult.pathOrNull?.createParentDirectories(), - ) + val result: SetupHandler.ArtifactsResult + val generatedIn = measureNanoTime { + result = setupService.get().generateArtifacts(context) + } + logger.lifecycle("Completed setup in ${formatNs(generatedIn)}") + + val copiedTime = measureNanoTime { + result.mainOutput.copyTo(mappedServerJar.path.createParentDirectories(), overwrite = true) + result.legacyOutput?.copyTo(legacyPaperclipResult.path.createParentDirectories(), overwrite = true) setupService.get().extractReobfMappings(reobfMappings.path.createParentDirectories()) } - logger.lifecycle("Completed setup in ${formatNs(took)}") + logger.lifecycle("Copied artifacts to project cache in ${formatNs(copiedTime)}") } } diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt index eca2caf9f..e740a630b 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v2/SetupHandlerImplV2.kt @@ -232,15 +232,12 @@ class SetupHandlerImplV2( } @Volatile - private var completedOutput: Pair? = null + private var completedOutput: SetupHandler.ArtifactsResult? = null @Synchronized - override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + override fun generateArtifacts(context: SetupHandler.ExecutionContext): SetupHandler.ArtifactsResult { if (completedOutput != null) { - val (main, legacy) = requireNotNull(completedOutput) - main.copyTo(output, true) - legacy.copyTo(requireNotNull(legacyOutput), true) - return + return requireNotNull(completedOutput) } val dispatcher = createDispatcher(context) @@ -251,9 +248,8 @@ class SetupHandlerImplV2( progressLogger.progress(it) } } - filter.get().copyTo(output, true) - paperclip.get().copyTo(requireNotNull(legacyOutput), true) - completedOutput = filter.get() to paperclip.get() + return SetupHandler.ArtifactsResult(filter.get(), paperclip.get()) + .also { completedOutput = it } } override fun extractReobfMappings(output: Path) { diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt index 6f00ee04b..8f11b355f 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/v5/SetupHandlerImplV5.kt @@ -213,13 +213,12 @@ class SetupHandlerImplV5( } @Volatile - private var completedOutput: Path? = null + private var completedOutput: SetupHandler.ArtifactsResult? = null @Synchronized - override fun generateCombinedOrClassesJar(context: SetupHandler.ExecutionContext, output: Path, legacyOutput: Path?) { + override fun generateArtifacts(context: SetupHandler.ExecutionContext): SetupHandler.ArtifactsResult { if (completedOutput != null) { - requireNotNull(completedOutput).copyTo(output, true) - return + return requireNotNull(completedOutput) } val dispatcher = createDispatcher(context) @@ -233,8 +232,8 @@ class SetupHandlerImplV5( progressLogger.progress(it) } } - request.get().copyTo(output, true) - completedOutput = request.get() + return SetupHandler.ArtifactsResult(request.get(), null) + .also { completedOutput = it } } override fun extractReobfMappings(output: Path) { From cb495f7cbb36326e565a0b10b282140125119d23 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 21:31:43 -0800 Subject: [PATCH 07/10] Adjust cache cleaning log message --- .../papermc/paperweight/userdev/internal/action/CacheCleaner.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt index 511ed4c30..cd2d33581 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/action/CacheCleaner.kt @@ -65,6 +65,6 @@ class CacheCleaner(private val work: Path) { val took = System.nanoTime() - start val level = if (deleted > 0) LogLevel.LIFECYCLE else LogLevel.INFO - logger.log(level, "paperweight-userdev: Cleaned $deleted files totaling ${deletedSize / 1024}KB in ${took / 1_000_000}ms") + logger.log(level, "paperweight-userdev: Deleted $deleted expired cache entries totaling ${deletedSize / 1024}KB in ${took / 1_000_000}ms") } } From 3b46afa36384595dfd083387077ba4cfb89473a5 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:08:06 -0800 Subject: [PATCH 08/10] Remove check for cache cleaning in afterEvaluate logic Cleaning caches and building in the same gradle invocation now works properly --- .../paperweight/userdev/PaperweightUser.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt index 8f7c0173d..380a4530c 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt @@ -56,7 +56,6 @@ import org.gradle.build.event.BuildEventsListenerRegistry import org.gradle.internal.DefaultTaskExecutionRequest import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.kotlin.dsl.* -import org.gradle.util.internal.NameMatcher abstract class PaperweightUser : Plugin { @@ -165,10 +164,6 @@ abstract class PaperweightUser : Plugin { applyJunitExclusionRule() } - if (cleaningCache(cleanCache, cleanAll)) { - return@afterEvaluate - } - if (isIDEASync()) { val startParameter = gradle.startParameter val taskRequests = startParameter.taskRequests.toMutableList() @@ -193,20 +188,6 @@ abstract class PaperweightUser : Plugin { } } - private fun Project.cleaningCache(vararg cleanTasks: TaskProvider<*>): Boolean { - val cleanTaskNames = cleanTasks.map { it.name }.toSet() - - // Manually check if cleanCache is a target, and skip setup. - // Gradle moved NameMatcher to internal packages in 7.1, so this solution isn't ideal, - // but it does work and allows using the cleanCache task without setting up the workspace first - return gradle.startParameter.taskRequests - .any { req -> - req.args.any { arg -> - NameMatcher().find(arg, tasks.names) in cleanTaskNames - } - } - } - private fun Project.decorateJarManifests() { val op = Action { manifest { From cc0e079b16472300ce995d735d98e65d4a057917 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Thu, 26 Dec 2024 22:54:02 -0800 Subject: [PATCH 09/10] Add back cache cleaning check but fail the build during setup (at execution time) instead of simply skipping some configuration Cleaning the cache and running setup in the same build is now sometimes safe, but not always safe. Whereas before it was never safe. We will continue to disallow it generally. --- .../paperweight/userdev/PaperweightUser.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt index 380a4530c..620d1835c 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/PaperweightUser.kt @@ -56,6 +56,7 @@ import org.gradle.build.event.BuildEventsListenerRegistry import org.gradle.internal.DefaultTaskExecutionRequest import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.kotlin.dsl.* +import org.gradle.util.internal.NameMatcher abstract class PaperweightUser : Plugin { @@ -185,9 +186,29 @@ abstract class PaperweightUser : Plugin { // Clean v1 shared caches cleanSharedCaches(this, sharedCacheRoot) + + if (cleaningCache(cleanCache, cleanAll)) { + tasks.withType(UserdevSetupTask::class).configureEach { + doFirst { throw PaperweightException("Cannot run setup tasks when cleaning caches") } + } + } } } + private fun Project.cleaningCache(vararg cleanTasks: TaskProvider<*>): Boolean { + val cleanTaskNames = cleanTasks.map { it.name }.toSet() + + // Manually check if cleanCache is a target, and skip setup. + // Gradle moved NameMatcher to internal packages in 7.1, so this solution isn't ideal, + // but it does work and allows using the cleanCache task without setting up the workspace first + return gradle.startParameter.taskRequests + .any { req -> + req.args.any { arg -> + NameMatcher().find(arg, tasks.names) in cleanTaskNames + } + } + } + private fun Project.decorateJarManifests() { val op = Action { manifest { From 42f2bb95bafac76f4b379d54f2afce164bab765e Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Fri, 27 Dec 2024 11:38:27 -0800 Subject: [PATCH 10/10] Adjust unsupported dev bundle version message --- .../io/papermc/paperweight/userdev/internal/setup/DevBundles.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt index afb1779db..10ce56e79 100644 --- a/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt +++ b/paperweight-userdev/src/main/kotlin/io/papermc/paperweight/userdev/internal/setup/DevBundles.kt @@ -58,7 +58,7 @@ private fun readDevBundle( if (dataVersion !in supported) { throw PaperweightException( "The paperweight development bundle you are attempting to use is of data version '$dataVersion', but" + - " the currently running version of paperweight only supports data versions '$supported'." + " the currently running version of paperweight only supports data versions '${supported.keys}'." ) }