import org.gradle.api.DefaultTask import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskProvider import java.io.File import java.net.URI import java.security.MessageDigest /** * Custom Gradle plugin for automated Hytale server testing. * * Usage: * runHytale { * jarUrl = "https://example.com/hytale-server.jar" * } * * ./gradlew runServer */ open class RunHytalePlugin : Plugin { override fun apply(project: Project) { // Create extension for configuration val extension = project.extensions.create("runHytale", RunHytaleExtension::class.java) // Register the runServer task val runTask: TaskProvider = project.tasks.register( "runServer", RunServerTask::class.java ) { jarUrl.set(extension.jarUrl) group = "hytale" description = "Downloads and runs the Hytale server with your plugin" } // Make runServer depend on shadowJar (build plugin first) project.tasks.findByName("shadowJar")?.let { runTask.configure { dependsOn(it) } } } } /** * Extension for configuring the RunHytale plugin. */ open class RunHytaleExtension { var jarUrl: String = "https://example.com/hytale-server.jar" } /** * Task that downloads, sets up, and runs a Hytale server with the plugin. */ open class RunServerTask : DefaultTask() { @Input val jarUrl = project.objects.property(String::class.java) @TaskAction fun run() { // Create directories val runDir = File(project.projectDir, "run").apply { mkdirs() } val pluginsDir = File(runDir, "plugins").apply { mkdirs() } val jarFile = File(runDir, "server.jar") // Cache directory for downloaded server JARs val cacheDir = File( project.layout.buildDirectory.asFile.get(), "hytale-cache" ).apply { mkdirs() } // Compute hash of URL for caching val urlHash = MessageDigest.getInstance("SHA-256") .digest(jarUrl.get().toByteArray()) .joinToString("") { "%02x".format(it) } val cachedJar = File(cacheDir, "$urlHash.jar") // Download server JAR if not cached if (!cachedJar.exists()) { println("Downloading Hytale server from ${jarUrl.get()}") try { URI.create(jarUrl.get()).toURL().openStream().use { input -> cachedJar.outputStream().use { output -> input.copyTo(output) } } println("Server JAR downloaded and cached") } catch (e: Exception) { println("ERROR: Failed to download server JAR") println("Make sure the jarUrl in build.gradle.kts is correct") println("Error: ${e.message}") return } } else { println("Using cached server JAR") } // Copy server JAR to run directory cachedJar.copyTo(jarFile, overwrite = true) // Copy plugin JAR to plugins folder project.tasks.findByName("shadowJar")?.outputs?.files?.firstOrNull()?.let { shadowJar -> val targetFile = File(pluginsDir, shadowJar.name) shadowJar.copyTo(targetFile, overwrite = true) println("Plugin copied to: ${targetFile.absolutePath}") } ?: run { println("WARNING: Could not find shadowJar output") } println("Starting Hytale server...") println("Press Ctrl+C to stop the server") // Check if debug mode is enabled val debugMode = project.hasProperty("debug") val javaArgs = mutableListOf() if (debugMode) { javaArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005") println("Debug mode enabled. Connect debugger to port 5005") } javaArgs.addAll(listOf("-jar", jarFile.name)) // Start the server process val process = ProcessBuilder("java", *javaArgs.toTypedArray()) .directory(runDir) .start() // Handle graceful shutdown project.gradle.buildFinished { if (process.isAlive) { println("\nStopping server...") process.destroy() } } // Forward stdout to console Thread { process.inputStream.bufferedReader().useLines { lines -> lines.forEach { println(it) } } }.start() // Forward stderr to console Thread { process.errorStream.bufferedReader().useLines { lines -> lines.forEach { System.err.println(it) } } }.start() // Forward stdin to server (for commands) Thread { System.`in`.bufferedReader().useLines { lines -> lines.forEach { process.outputStream.write((it + "\n").toByteArray()) process.outputStream.flush() } } }.start() // Wait for server to exit val exitCode = process.waitFor() println("Server exited with code $exitCode") } }