Skip to content

Commit

Permalink
Support hotreloading of shared common modules too (#1405)
Browse files Browse the repository at this point in the history
  • Loading branch information
soywiz committed Mar 13, 2023
1 parent 4368656 commit cf7d302
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.soywiz.korge.gradle.targets.jvm

import com.soywiz.korge.gradle.*
import com.soywiz.korge.gradle.kotlin
import com.soywiz.korge.gradle.targets.*
import com.soywiz.korlibs.*
import org.gradle.api.*
import org.gradle.api.artifacts.*
import org.gradle.api.file.*
import org.gradle.api.tasks.*
import org.gradle.jvm.tasks.*
import java.io.*

fun Project.findAllProjectDependencies(visited: MutableSet<Project> = mutableSetOf()): Set<Project> {
if (this in visited) return visited
visited.add(this)
val dependencies = project.configurations.flatMap { it.dependencies.withType(ProjectDependency::class.java) }.filterIsInstance<ProjectDependency>()
return (dependencies.flatMap { it.dependencyProject.findAllProjectDependencies(visited) } + this).toSet()
}

open class KorgeJavaExecWithAutoreload : KorgeJavaExec() {
@get:Input
var enableRedefinition: Boolean = false
Expand All @@ -22,41 +28,54 @@ open class KorgeJavaExecWithAutoreload : KorgeJavaExec() {
const val CMD_SEPARATOR = "<@/@>"
}

private var projectPath: String = project.path
private lateinit var projectPaths: List<String>
private var rootDir: File = project.rootProject.rootDir
@get:InputFiles
lateinit var rootJars: FileCollection
lateinit var rootJars: List<File>
@get:InputFile
//private var reloadAgentConfiguration: Configuration = project.configurations.getByName(KORGE_RELOAD_AGENT_CONFIGURATION_NAME)//.resolve().first()
lateinit var reloadAgentJar: File

init {
//val reloadAgent = project.findProject(":korge-reload-agent")
//if (reloadAgent != null)
rootJars = (project.tasks.findByName("compileKotlinJvm") as org.jetbrains.kotlin.gradle.tasks.KotlinCompile).outputs.files
//project.dependencies.add()
project.afterEvaluate {
val allProjects = project.findAllProjectDependencies()
//projectPaths = allProjects.map { it.path }
projectPaths = listOf(project.path)
rootJars = allProjects.map { File(it.buildDir, "classes/kotlin/jvm/main") }
//println("allProjects=${allProjects.map { it.name }}")
//println("projectPaths=$projectPaths")
//println("rootJars=\n${rootJars.toList().joinToString("\n")}")
}
/*
project.afterEvaluate {
project.afterEvaluate {
project.afterEvaluate {
//project.configurations.getByName("compile")
//println("*****" + project.findAllProjectDependencies())
val allProjects = project.findAllProjectDependencies()
val allProjectsWithCompileKotinJvm = allProjects.filter { it.tasks.findByName("compileKotlinJvm") != null }
projectPaths = allProjectsWithCompileKotinJvm.map { it.path }
rootJars = allProjectsWithCompileKotinJvm
.mapNotNull { (it.tasks.findByName("compileKotlinJvm") as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile?)?.outputs?.files }
.reduce { a, b -> a + b }
println("allProjects=${allProjects.map { it.name }}")
println("allProjectsWithCompileKotinJvm=${allProjectsWithCompileKotinJvm.map { it.name }}")
println("rootJars=\n${rootJars.toList().joinToString("\n")}")
//println("::::" + project.configurations.toList())
}
}
}
*/
val reloadAgent = project.findProject(":korge-reload-agent")
reloadAgentJar = when {
reloadAgent != null -> (project.rootProject.tasks.getByPath(":korge-reload-agent:jar") as Jar).outputs.files.files.first()
else -> project.configurations.getByName(KORGE_RELOAD_AGENT_CONFIGURATION_NAME).resolve().first()
}
}

override fun autoconfigure() {
//super.autoconfigure()
////println("---------------")
////println((project.tasks.findByName("compileKotlinJvm") as org.jetbrains.kotlin.gradle.tasks.KotlinCompile).outputs.files.toList())
//
//project.afterEvaluate {
// //println("++++++++++++++")
// rootJars = (project.tasks.findByName("compileKotlinJvm") as org.jetbrains.kotlin.gradle.tasks.KotlinCompile).outputs.files
// val reloadAgent = project.findProject(":korge-reload-agent")
// reloadAgentJar = when {
// reloadAgent != null -> (project.rootProject.tasks.getByPath(":korge-reload-agent:jar") as Jar).outputs.files.files.first()
// else -> project.configurations.getByName(KORGE_RELOAD_AGENT_CONFIGURATION_NAME).resolve().first()
// }
//}
}

override fun exec() {
//val gradlewCommand = if (isWindows) "gradlew.bat" else "gradlew"

Expand All @@ -83,7 +102,11 @@ open class KorgeJavaExecWithAutoreload : KorgeJavaExec() {
add("--configuration-cache-problems=warn")
}
add("-t")
add("${projectPath.trimEnd(':')}:compileKotlinJvmAndNotify")
add("compileKotlinJvm")
//add("compileKotlinJvmAndNotify")
for (projectPath in projectPaths) {
add("${projectPath.trimEnd(':')}:compileKotlinJvmAndNotify")
}
}.joinToString(CMD_SEPARATOR),
"$enableRedefinition",
rootJars.joinToString(CMD_SEPARATOR) { it.absolutePath }
Expand All @@ -95,11 +118,8 @@ open class KorgeJavaExecWithAutoreload : KorgeJavaExec() {
}
}

open class KorgeJavaExec : JavaExec() {
//dependsOn(getKorgeProcessResourcesTaskName("jvm", "main"))

@get:InputFiles
val korgeClassPath: FileCollection = ArrayList<FileCollection>().apply {
fun Project.getKorgeClassPath(): FileCollection {
return ArrayList<FileCollection>().apply {
val mainJvmCompilation = project.mainJvmCompilation
add(mainJvmCompilation.runtimeDependencyFiles)
add(mainJvmCompilation.compileDependencyFiles)
Expand All @@ -112,10 +132,13 @@ open class KorgeJavaExec : JavaExec() {
//add(project.files().from((project.tasks.findByName(jvmProcessedResourcesTaskName) as KorgeProcessedResourcesTask).processedResourcesFolder))
}
.reduceRight { l, r -> l + r }
}

open fun autoconfigure() {
open class KorgeJavaExec : JavaExec() {
//dependsOn(getKorgeProcessResourcesTaskName("jvm", "main"))

}
@get:InputFiles
val korgeClassPath: FileCollection = project.getKorgeClassPath()

override fun exec() {
val firstThread = firstThread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ object KorgeReloadAgent {
println("[KorgeReloadAgent] - Running $continuousCommand")
while (true) {
try {
val isWindows = System.getProperty("os.name").toLowerCase().contains("win")
val isWindows = System.getProperty("os.name").lowercase().contains("win")
//val args = arrayOf<String>()
//val args = if (isWindows) arrayOf("cmd.exe", "/k") else arrayOf("/bin/sh", "-c")
//val args = if (isWindows) arrayOf() else arrayOf("/bin/sh", "-c")
Expand Down Expand Up @@ -219,7 +219,7 @@ class KorgeReloaderProcessor(val rootFolders: List<String>, val inst: Instrument
if (modifiedClassNames.isEmpty()) {
println("[KorgeReloadAgent] modifiedClassNames=$modifiedClassNames [EMPTY] STOPPING")
} else {
println("[KorgeReloadAgent] modifiedClassNames=$modifiedClassNames")
println("[KorgeReloadAgent] modifiedClassNames=\n${modifiedClassNames.joinToString("\n")}")
var successRedefinition = true
val changedDefinitions = arrayListOf<ClassDefinition>()
val times = arrayListOf<Long>()
Expand Down Expand Up @@ -256,8 +256,8 @@ class KorgeReloaderProcessor(val rootFolders: List<String>, val inst: Instrument
successRedefinition = false
}
println("[KorgeReloadAgent] reload enableRedefinition=$enableRedefinition, successRedefinition=$successRedefinition, changedDefinitions=${changedDefinitions.size}, classNameToBytes=${classNameToBytes.size}, times[${times.size}]=${times.sum()}ms")
val triggerReload = Class.forName("com.soywiz.korge.KorgeReload").getMethod("triggerReload", java.util.List::class.java, java.lang.Boolean.TYPE)
triggerReload.invoke(null, changedDefinitions.map { it.definitionClass.name }, successRedefinition)
val triggerReload = Class.forName("com.soywiz.korge.KorgeReload").getMethod("triggerReload", java.util.List::class.java, java.lang.Boolean.TYPE, java.util.List::class.java)
triggerReload.invoke(null, changedDefinitions.map { it.definitionClass.name }, successRedefinition, rootFolders)
}
}

Expand Down
11 changes: 6 additions & 5 deletions korge/src/commonMain/kotlin/com/soywiz/korge/KorgeReload.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import kotlin.jvm.JvmStatic
import kotlin.native.concurrent.ThreadLocal
import kotlin.reflect.KClass

class ReloadClassContext(val injector: AsyncInjector, val refreshedClasses: Set<String>)
class ReloadClassContext(val injector: AsyncInjector, val refreshedClasses: Set<String>, val rootFolders: List<String>)

internal open class KorgeReloadInternalImpl {
open fun <T : Any> getReloadedClass(clazz: KClass<T>, context: ReloadClassContext): KClass<T> = clazz
Expand All @@ -23,9 +23,9 @@ object KorgeReload {

@JvmStatic
@Suppress("unused") // This is called from [com.soywiz.korge.reloadagent.KorgeReloadAgent]
fun triggerReload(classes: List<String>, success: Boolean) {
fun triggerReload(classes: List<String>, success: Boolean, rootFolders: List<String>) {
println("KorgeReloadAgent detected a class change. Reload: $classes")
KorgeReload_eventDispatcher?.dispatch(ReloadEvent(classes.toSet(), success))
KorgeReload_eventDispatcher?.dispatch(ReloadEvent(classes.toSet(), success, rootFolders))
}

fun registerEventDispatcher(eventDispatcher: EventListener) {
Expand All @@ -45,13 +45,14 @@ object KorgeReload {
data class ReloadEvent(
val refreshedClasses: Set<String>,
/** Was able to reload all classes successfully in the existing class loader */
val reloadSuccess: Boolean
val reloadSuccess: Boolean,
val rootFolders: List<String>
) : Event(), TEvent<ReloadEvent> {
override val type: EventType<ReloadEvent> = ReloadEvent
companion object : EventType<ReloadEvent>

val doFullReload: Boolean get() = !reloadSuccess
fun <T : Any> getReloadedClass(clazz: KClass<T>, injector: AsyncInjector): KClass<T> = KorgeReloadInternal.getReloadedClass(clazz, ReloadClassContext(injector, refreshedClasses))
fun <T : Any> getReloadedClass(clazz: KClass<T>, injector: AsyncInjector): KClass<T> = KorgeReloadInternal.getReloadedClass(clazz, ReloadClassContext(injector, refreshedClasses, rootFolders))
fun transferKeepProperties(old: Any, new: Any) = KorgeReloadInternal.transferKeepProperties(old, new)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal actual val KorgeReloadInternal: KorgeReloadInternalImpl = object : Korg
override fun <T : Any> getReloadedClass(clazz: KClass<T>, context: ReloadClassContext): KClass<T> {
println("### KorgeReload_getReloadedClass: $clazz")
val oldClass = clazz
val newClass = KorgeReloadClassLoader().loadClass(oldClass.qualifiedName).kotlin as KClass<T>
val newClass = KorgeReloadClassLoader(extraFolders = context.rootFolders).loadClass(oldClass.qualifiedName).kotlin as KClass<T>
context.injector.jvmRemoveMappingsByClassName(context.refreshedClasses)
context.injector.removeMapping(oldClass)
context.injector.root.jvmAutomapping()
Expand Down Expand Up @@ -52,18 +52,19 @@ internal actual val KorgeReloadInternal: KorgeReloadInternalImpl = object : Korg
}

class KorgeReloadClassLoader(
val folders: List<File> = System.getProperty("java.class.path")
.split(File.pathSeparator)
.map { File(it) }
.filter { !it.name.endsWith(".jar") }
, parent: ClassLoader? = null
val extraFolders: List<String> = emptyList(),
val allEntries: List<File> = (extraFolders + System.getProperty("java.class.path").split(File.pathSeparator)).map { File(it).absoluteFile }.distinct(),
val jars: List<File> = allEntries.filter { it.name.endsWith(".jar") },
val folders: List<File> = allEntries.filter { !it.name.endsWith(".jar") },
parent: ClassLoader? = null
) : ClassLoader(parent ?: ClassLoader.getSystemClassLoader()) {
companion object {
val logger = Logger("KorgeReloadClassLoader")
}

init {
logger.info { "KorgeReloadClassLoader:\n${folders.joinToString("\n")}" }
logger.info { "KorgeReloadClassLoader.jars:\n${jars.joinToString("\n")}" }
logger.info { "KorgeReloadClassLoader.folders:\n${folders.joinToString("\n")}" }
}

override fun loadClass(name: String, resolve: Boolean): Class<*> {
Expand Down

0 comments on commit cf7d302

Please sign in to comment.