html/compose-compiler-integration/build.gradle.kts (198 lines of code) (raw):

import org.jetbrains.compose.gradle.standardConf plugins { kotlin("multiplatform") id("org.jetbrains.compose") id("org.jetbrains.kotlin.plugin.compose") } kotlin { js(IR) { browser() { testTask { useKarma { standardConf() } } } binaries.executable() } sourceSets { val jsMain by getting { dependencies { implementation(project(":compose-compiler-integration-lib")) implementation(kotlin("stdlib-js")) implementation(compose.runtime) implementation(project(":html-core")) implementation(libs.kotlinx.coroutines.core) } } val jsTest by getting { dependencies { implementation(kotlin("test-js")) } } } } fun cloneTemplate(templateName: String, contentMain: String, contentLib: String): File { val tempDir = layout.buildDirectory.dir("temp/cloned-$templateName").get().asFile tempDir.deleteRecursively() tempDir.mkdirs() file("${projectDir.absolutePath}/main-template").copyRecursively(tempDir) // tempDir.deleteOnExit() File("$tempDir/src/commonMain/kotlin/Main.kt").printWriter().use { out -> out.println(contentMain) } File("$tempDir/lib/src/commonMain/kotlin/Lib.kt").printWriter().use { out -> out.println(contentLib) } return tempDir } private fun build( caseName: String, directory: File, failureExpected: Boolean = false, composeVersion: String, kotlinVersion: String, vararg buildCmd: String = arrayOf("build", "jsNodeDevelopmentRun") ) { val isWin = System.getProperty("os.name").startsWith("Win") val arguments = buildCmd.toMutableList().also { it.add("-Pcompose.version=$composeVersion") it.add("-Pkotlin.version=$kotlinVersion") it.add("--stacktrace") it.add("--info") }.toTypedArray() val gradlewFile = File(directory, if (isWin) "gradlew.bat" else "gradlew") println("[compose-compiler-integration] Working directory: ${directory.absolutePath}") if (!gradlewFile.exists()) { throw GradleException("gradlew not found in ${directory.absolutePath}. Please ensure the Gradle wrapper is present.") } if (!isWin) { if (!gradlewFile.canExecute()) { val isExecutable = gradlewFile.setExecutable(true) if (!isExecutable) { throw GradleException("Failed to make gradlew executable: ${gradlewFile.absolutePath}") } } } val command: List<String> = if (isWin) { listOf("cmd", "/c", "gradlew.bat") + arguments } else { listOf("./gradlew") + arguments } println("[compose-compiler-integration] Executing: ${command.joinToString(" ")}") val proc = try { ProcessBuilder(command) .directory(directory) .redirectOutput(ProcessBuilder.Redirect.INHERIT) .redirectError(ProcessBuilder.Redirect.INHERIT) .start() } catch (e: Exception) { throw GradleException("Failed to start Gradle process. Command: ${command.joinToString(" ")}", e) } val finished = proc.waitFor(10, TimeUnit.MINUTES) if (!finished) { proc.destroyForcibly() throw GradleException("Gradle process timed out for $caseName. Command: ${command.joinToString(" ")}") } if (proc.exitValue() != 0 && !failureExpected) { throw GradleException("Error compiling $caseName (exit code ${proc.exitValue()})") } if (failureExpected && proc.exitValue() == 0) { throw AssertionError("$caseName compilation did not fail!!!") } } data class RunChecksResult( val cases: Map<String, Throwable?> ) { val totalCount = cases.size val failedCount = cases.filter { it.value != null }.size val hasFailed = failedCount > 0 fun printResults() { cases.forEach { (name, throwable) -> println(name + " : " + (throwable ?: "OK")) } } fun reportToTeamCity() { cases.forEach { (caseName, error) -> println("##teamcity[testStarted name='compileTestCase_$caseName']") if (error != null) { println("##teamcity[testFailed name='compileTestCase_$caseName']") } println("##teamcity[testFinished name='compileTestCase_$caseName']") } } } fun runCasesInDirectory( dir: File, filterPath: String, expectCompilationError: Boolean, composeVersion: String, kotlinVersion: String ): RunChecksResult { return dir.listFiles()!!.filter { it.absolutePath.contains(filterPath) }.mapIndexed { _, file -> println("Running check for ${file.name}, expectCompilationError = $expectCompilationError, composeVersion = $composeVersion") val contentLines = file.readLines() val startMainLineIx = contentLines.indexOf("// @Module:Main").let { ix -> if (ix == -1) 0 else ix + 1 } val startLibLineIx = contentLines.indexOf("// @Module:Lib").let { ix -> if (ix == -1) contentLines.size else ix - 1 } require(startMainLineIx < startLibLineIx) { "The convention is that @Module:Lib should go after @Module:Main" } val mainContent = contentLines.let { lines -> val endLineIx = if (startLibLineIx < lines.size) startLibLineIx - 1 else lines.lastIndex lines.slice(startMainLineIx..endLineIx).joinToString(separator = "\n") } val libContent = contentLines.let { lines -> if (startLibLineIx < lines.size) { lines.slice(startLibLineIx..lines.lastIndex) } else { emptyList() }.joinToString(separator = "\n") } val caseName = file.name val tmpDir = cloneTemplate(caseName, contentMain = mainContent, contentLib = libContent) caseName to kotlin.runCatching { build( caseName = caseName, directory = tmpDir, failureExpected = expectCompilationError, composeVersion = composeVersion, kotlinVersion = kotlinVersion ) }.exceptionOrNull() }.let { RunChecksResult(it.toMap()) } } tasks.register("checkComposeCases") { doLast { val filterCases = project.findProperty("FILTER_CASES")?.toString() ?: "" val composeVersion = project.findProperty("compose.version")?.toString() ?: "0.0.0-SNASPHOT" val kotlinVersion = kotlin.coreLibrariesVersion val expectedFailingCasesDir = File("${projectDir.absolutePath}/testcases/failing") val expectedFailingResult = runCasesInDirectory( dir = expectedFailingCasesDir, expectCompilationError = true, filterPath = filterCases, composeVersion = composeVersion, kotlinVersion = kotlinVersion ) val passingCasesDir = File("${projectDir.absolutePath}/testcases/passing") val passingResult = runCasesInDirectory( dir = passingCasesDir, expectCompilationError = false, filterPath = filterCases, composeVersion = composeVersion, kotlinVersion = kotlinVersion ) expectedFailingResult.printResults() expectedFailingResult.reportToTeamCity() passingResult.printResults() passingResult.reportToTeamCity() if (expectedFailingResult.hasFailed || passingResult.hasFailed) { error("There were failed cases. Check the logs above") } } }