diff --git a/processor/src/main/kotlin/com/lightningkite/rock/AnnotationProcessor.kt b/processor/src/main/kotlin/com/lightningkite/rock/AnnotationProcessor.kt index a3957fc7..0f40be2a 100644 --- a/processor/src/main/kotlin/com/lightningkite/rock/AnnotationProcessor.kt +++ b/processor/src/main/kotlin/com/lightningkite/rock/AnnotationProcessor.kt @@ -3,6 +3,7 @@ package com.lightningkite.kiteui import com.google.devtools.ksp.getClassDeclarationByName import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.symbol.* +import com.lightningkite.rock.CommonSymbolProcessor2 import java.io.BufferedWriter import java.io.File import java.util.* @@ -14,9 +15,18 @@ var khrysalisUsed = false class RouterGeneration( val codeGenerator: CodeGenerator, val logger: KSPLogger, -) : CommonSymbolProcessor(codeGenerator) { - override fun process2(resolver: Resolver) { - val allRoutables = resolver.getAllFiles() +) : CommonSymbolProcessor2(codeGenerator, "kiteui", 0) { + override fun interestedIn(resolver: Resolver): Set { + val allRoutables = resolver.getAllFiles().filter { + it.declarations + .filterIsInstance() + .any { it.annotation("Routable") != null } + }.toSet() + return allRoutables + } + + override fun process2(resolver: Resolver, files: Set) { + val allRoutables = files .flatMap { it.declarations } .filterIsInstance() .filter { it.annotation("Routable") != null } @@ -92,7 +102,7 @@ class RouterGeneration( } } appendLine("}") - }else { + } else { appendLine("${routable.source.simpleName!!.asString()}(") tab { for ((index, part) in route.withIndex()) { @@ -127,6 +137,7 @@ class RouterGeneration( when (it) { is ParsedRoutable.Segment.Constant -> "\"${it.value}\"" is ParsedRoutable.Segment.Variable -> "UrlProperties.encodeToString(it.${it.name})" + else -> throw Exception() } } appendLine("${routable.source.simpleName!!.asString()}::class to label@{") diff --git a/processor/src/main/kotlin/com/lightningkite/rock/CommonSymbolProcessor.kt b/processor/src/main/kotlin/com/lightningkite/rock/CommonSymbolProcessor.kt deleted file mode 100644 index f6ee2dd8..00000000 --- a/processor/src/main/kotlin/com/lightningkite/rock/CommonSymbolProcessor.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.lightningkite.kiteui - -import com.google.devtools.ksp.processing.* -import com.google.devtools.ksp.symbol.KSAnnotated -import java.io.BufferedWriter -import java.io.File - -abstract class CommonSymbolProcessor( - private val myCodeGenerator: CodeGenerator -): SymbolProcessor { - abstract fun process2(resolver: Resolver) - - lateinit var fileCreator: (dependencies: Dependencies, packageName: String, fileName: String, extensionName: String) -> BufferedWriter - - var invoked = false - final override fun process(resolver: Resolver): List { - if (invoked) return listOf() - invoked = true - - val stub = myCodeGenerator.createNewFile( - Dependencies.ALL_FILES, - fileName = "test", - extensionName = "txt", - packageName = "com.lightningkite.lightningserver" - ).writer().use { println("Will generate in common folder") } - val outSample = myCodeGenerator.generatedFile.first().absoluteFile - val projectFolder = generateSequence(outSample) { it.parentFile!! } - .first { it.name == "build" } - .parentFile!! - val flavor = outSample.path.split(File.separatorChar) - .dropWhile { it != "ksp" } - .drop(2) - .first() - .let { - if(it.contains("test", true)) "Test" - else "Main" - } - val outFolder = projectFolder.resolve("build/generated/ksp/common/common$flavor/kotlin") - outFolder.mkdirs() - val createdFiles = HashSet() - val common = resolver.getAllFiles().any { it.filePath?.contains("/src/common", true) == true } - - // Acquire lock - val lockFile = projectFolder.resolve("build/generated/ksp/.kiteui-lock") - if(lockFile.exists()) { - // A different process is doing KSP for us; bail after waiting a bit - while(lockFile.exists()) { - Thread.sleep(1000L) - } - return listOf() - } - try { - lockFile.createNewFile() - - fileCreator = label@{ dependencies, packageName, fileName, extensionName -> - if (!common) return@label myCodeGenerator.createNewFile( - dependencies, - packageName, - fileName, - extensionName - ).bufferedWriter() - val packagePath = packageName.split('.').filter { it.isNotBlank() }.joinToString("") { "$it/" } - outFolder.resolve("${packagePath}$fileName.$extensionName") - .also { it.parentFile.mkdirs() } - .also { createdFiles += outFolder.resolve(it) } - .bufferedWriter() - } - process2(resolver) - val manifest = outFolder.parentFile!!.resolve("kiteui-manifest.txt") - manifest.takeIf { it.exists() }?.readLines() - ?.map { File(it) } - ?.toSet() - ?.minus(createdFiles) -// ?.forEach { outFolder.resolve(it).takeIf { it.exists() }?.delete() } - manifest.writeText(createdFiles.joinToString("\n") + "\n") - } finally { - lockFile.delete() - } - return listOf() - } - - fun createNewFile(dependencies: Dependencies, packageName: String, fileName: String, extensionName: String = "kt"): BufferedWriter { - return fileCreator(dependencies, packageName, fileName, extensionName) - } -} \ No newline at end of file diff --git a/processor/src/main/kotlin/com/lightningkite/rock/CommonSymbolProcessor2.kt b/processor/src/main/kotlin/com/lightningkite/rock/CommonSymbolProcessor2.kt new file mode 100644 index 00000000..7f35edee --- /dev/null +++ b/processor/src/main/kotlin/com/lightningkite/rock/CommonSymbolProcessor2.kt @@ -0,0 +1,157 @@ +package com.lightningkite.rock + +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSFile +import java.io.BufferedWriter +import java.io.File +import java.io.RandomAccessFile +import java.io.Writer +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.nio.channels.OverlappingFileLockException +import java.nio.file.Files + +abstract class CommonSymbolProcessor2( + private val myCodeGenerator: CodeGenerator, + val myId: String, + val version: Int = 0 +) : SymbolProcessor { + abstract fun process2(resolver: Resolver, files: Set) + abstract fun interestedIn(resolver: Resolver): Set + + private lateinit var fileCreator: (dependencies: Dependencies, packageName: String, fileName: String, extensionName: String) -> Writer + + private var invoked = false + final override fun process(resolver: Resolver): List { + if (invoked) return listOf() + invoked = true + + val interestedIn = interestedIn(resolver) + + val stub = myCodeGenerator.createNewFile( + Dependencies.ALL_FILES, + fileName = "$myId", + extensionName = "txt", + packageName = "com.lightningkite.lightningserver" + ).writer().use { + it.appendLine("Will generate in common folder; analyzed files below") + interestedIn.forEach { f -> it.appendLine(f.filePath) } + } + val outSample = myCodeGenerator.generatedFile.first().absoluteFile + val projectFolder = generateSequence(outSample) { it.parentFile!! } + .first { it.name == "build" } + .parentFile!! + val common = interestedIn.any { it.filePath?.contains("/src/common", true) == true } + val flavor = outSample.path.split(File.separatorChar) + .dropWhile { it != "ksp" } + .drop(2) + .first() + .let { + if (it.contains("test", true)) "Test" + else "Main" + } + val outFolder = projectFolder.resolve("build/generated/ksp/common/common$flavor/kotlin") + outFolder.mkdirs() + + if(common) { + processFiles( + version = version, + dependencies = interestedIn.asSequence().map { it.filePath.let(::File) }, + lockFile = outFolder.resolve("$myId.lock"), + destinationFolder = outFolder.resolve(myId).also { it.mkdirs() }, + action = { + fileCreator = label@{ dependencies, packageName, fileName, extensionName -> + val packagePath = packageName.split('.').filter { it.isNotBlank() }.joinToString("") { "$it/" } + this.file("${packagePath}$fileName.$extensionName") + } + process2(resolver, interestedIn) + } + ) + } else { + fileCreator = label@{ dependencies, packageName, fileName, extensionName -> + myCodeGenerator.createNewFile( + dependencies, + packageName, + fileName, + extensionName + ).bufferedWriter() + } + process2(resolver, interestedIn) + } + + return listOf() + } + + fun createNewFile( + dependencies: Dependencies, + packageName: String, + fileName: String, + extensionName: String = "kt" + ): Writer { + return fileCreator(dependencies, packageName, fileName, extensionName) + } +} + + + +fun Sequence.checksum() = sumOf { it.readText().hashCode() } +interface FileGenerator { + fun file(name: String): Writer +} + +fun processFiles( + version: Int, + dependencies: Sequence, + lockFile: File, + destinationFolder: File, + action: FileGenerator.() -> Unit +) { + val hash = dependencies.checksum() + version + while (true) { + try { + RandomAccessFile(lockFile, "rw").channel.use { channel -> + channel.lock().use { + channel.position(0) + val lastHash = channel.readInt() + if (lastHash == hash) return + val temp = Files.createTempDirectory("kspTemp").toFile() + try { + action(object : FileGenerator { + override fun file(name: String): Writer { + return temp.resolve(name).also { + it.parentFile.mkdirs() + }.bufferedWriter() + } + }) + destinationFolder.deleteRecursively() + temp.renameTo(destinationFolder) + } catch (e: Exception) { + // abandon + e.printStackTrace() + } + val out = ByteBuffer.allocate(4) + out.putInt(hash) + out.flip() + channel.position(0).write(out) + } + } + break + } catch (e: OverlappingFileLockException) { + Thread.sleep(100) + } + } +} + +fun FileChannel.readInt(): Int { + position(0) + val buff = ByteBuffer.allocate(1024) + var total = 0 + while (total < 4) { + val bytesRead = read(buff) + if (bytesRead == -1) return 0 + total += bytesRead + } + buff.flip() + return buff.getInt() +} \ No newline at end of file