diff --git a/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt b/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt index fb2311b..cf1e915 100644 --- a/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt +++ b/compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt @@ -58,68 +58,73 @@ public class ComposableStabilityCollector(private val project: Project) { val contentRoots = moduleRootManager.contentRoots for (contentRoot in contentRoots) { - // Look for build/stability/stability-info.json - val jsonFile = File(contentRoot.path, "build/stability/stability-info.json") - if (!jsonFile.exists()) { - continue - } - - try { - val jsonContent = jsonFile.readText() - val jsonObject = JsonParser.parseString(jsonContent).asJsonObject - val composablesArray = jsonObject.getAsJsonArray("composables") + val rootStabilityFolder = File(contentRoot.path, "build/stability") + val variantFolders = rootStabilityFolder.listFiles().orEmpty().filter { it.isDirectory } + val allStabilityFolders = variantFolders + rootStabilityFolder + for (folder in allStabilityFolders) { + // Look for build/stability/stability-info.json + val jsonFile = File(folder, "stability-info.json") + if (!jsonFile.exists()) { + continue + } - for (composableElement in composablesArray) { - val composableJson = composableElement.asJsonObject + try { + val jsonContent = jsonFile.readText() + val jsonObject = JsonParser.parseString(jsonContent).asJsonObject + val composablesArray = jsonObject.getAsJsonArray("composables") - // Skip anonymous composables - val simpleName = composableJson.get("simpleName").asString - if (simpleName == "") { - continue - } + for (composableElement in composablesArray) { + val composableJson = composableElement.asJsonObject - val qualifiedName = composableJson.get("qualifiedName").asString - val skippable = composableJson.get("skippable").asBoolean - val restartable = composableJson.get("restartable").asBoolean - - // Parse parameters - val parametersArray = composableJson.getAsJsonArray("parameters") - val parameters = parametersArray.map { paramElement -> - val paramJson = paramElement.asJsonObject - val stability = paramJson.get("stability").asString - ParameterInfo( - name = paramJson.get("name").asString, - type = paramJson.get("type").asString, - isStable = stability == "STABLE", - isRuntime = stability == "RUNTIME", - ) - } + // Skip anonymous composables + val simpleName = composableJson.get("simpleName").asString + if (simpleName == "") { + continue + } - // Try to find the source file and line number - val (filePath, fileName, line) = - ReadAction.compute, Exception> { - findSourceLocation(qualifiedName, simpleName) + val qualifiedName = composableJson.get("qualifiedName").asString + val skippable = composableJson.get("skippable").asBoolean + val restartable = composableJson.get("restartable").asBoolean + + // Parse parameters + val parametersArray = composableJson.getAsJsonArray("parameters") + val parameters = parametersArray.map { paramElement -> + val paramJson = paramElement.asJsonObject + val stability = paramJson.get("stability").asString + ParameterInfo( + name = paramJson.get("name").asString, + type = paramJson.get("type").asString, + isStable = stability == "STABLE", + isRuntime = stability == "RUNTIME", + ) } - val packageName = qualifiedName.substringBeforeLast(".$simpleName", "") - - composables.add( - ComposableInfo( - functionName = simpleName, - moduleName = module.name, - packageName = packageName.ifEmpty { "" }, - fileName = fileName, - filePath = filePath, - line = line, - isSkippable = skippable, - isRestartable = restartable, - isRuntime = !skippable && restartable, - parameters = parameters, - ), - ) + // Try to find the source file and line number + val (filePath, fileName, line) = + ReadAction.compute, Exception> { + findSourceLocation(qualifiedName, simpleName) + } + + val packageName = qualifiedName.substringBeforeLast(".$simpleName", "") + + composables.add( + ComposableInfo( + functionName = simpleName, + moduleName = module.name, + packageName = packageName.ifEmpty { "" }, + fileName = fileName, + filePath = filePath, + line = line, + isSkippable = skippable, + isRestartable = restartable, + isRuntime = !skippable && restartable, + parameters = parameters, + ), + ) + } + } catch (e: Exception) { + // Skip modules that fail to parse } - } catch (e: Exception) { - // Skip modules that fail to parse } } } diff --git a/stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt b/stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt index cf5db11..490e71c 100644 --- a/stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt +++ b/stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt @@ -72,12 +72,6 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { } else { registerTasksAndroid(target, extension, androidComponents) } - - // Add output parameter to the Kotlin tasks to ensure it is compatible with the Build Cache - target.tasks.withType(KotlinCompile::class.java).configureEach { - val stabilityDir = target.layout.buildDirectory.dir("stability").get() - outputs.dir(stabilityDir).optional(true) - } } private fun registerTasksNonAndroid( @@ -93,6 +87,9 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { stabilityInputFiles.setFrom( target.layout.buildDirectory.file("stability/stability-info.json"), ) + stabilityInputFiles.setFrom( + target.layout.buildDirectory.file("stability/test/stability-info.json"), + ) outputDir.set(extension.stabilityValidation.outputDir) ignoredPackages.set(extension.stabilityValidation.ignoredPackages) ignoredClasses.set(extension.stabilityValidation.ignoredClasses) @@ -109,6 +106,9 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { stabilityInputFiles.from( target.layout.buildDirectory.file("stability/stability-info.json"), ) + stabilityInputFiles.from( + target.layout.buildDirectory.file("stability/test/stability-info.json"), + ) stabilityReferenceFiles.from(extension.stabilityValidation.outputDir) ignoredPackages.set(extension.stabilityValidation.ignoredPackages) ignoredClasses.set(extension.stabilityValidation.ignoredClasses) @@ -131,6 +131,21 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { configureTaskDependencies(target, extension, null, stabilityDumpTask, stabilityCheckTask) addRuntimeDependency(target) } + + // Add output parameter to the Kotlin tasks to ensure it is compatible with the Build Cache + target.tasks.withType(KotlinCompile::class.java) + .named { + isKotlinTaskApplicable( + it, + extension.stabilityValidation.includeTests.get(), + ) + } + .configureEach { + val stabilityDir = target.layout.buildDirectory + .dir(getKotlinTaskStabilityFolderName(project, name)) + .get() + outputs.dir(stabilityDir).optional(true) + } } private fun registerTasksAndroid( @@ -158,7 +173,17 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { ) { projectName.set(target.name) stabilityInputFiles.setFrom( - target.layout.buildDirectory.file("stability/stability-info.json"), + target.layout.buildDirectory.file("stability/${variant.name}/stability-info.json"), + ) + stabilityInputFiles.setFrom( + target.layout.buildDirectory.file( + "stability/${variant.name}UnitTest/stability-info.json", + ), + ) + stabilityInputFiles.setFrom( + target.layout.buildDirectory.file( + "stability/${variant.name}AndroidTest/stability-info.json", + ), ) outputDir.set(extension.stabilityValidation.outputDir) ignoredPackages.set(extension.stabilityValidation.ignoredPackages) @@ -175,7 +200,17 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { ) { projectName.set(target.name) stabilityInputFiles.from( - target.layout.buildDirectory.file("stability/stability-info.json"), + target.layout.buildDirectory.file("stability/${variant.name}/stability-info.json"), + ) + stabilityInputFiles.from( + target.layout.buildDirectory.file( + "stability/${variant.name}UnitTest/stability-info.json", + ), + ) + stabilityInputFiles.from( + target.layout.buildDirectory.file( + "stability/${variant.name}AndroidTest/stability-info.json", + ), ) stabilityReferenceFiles.from(extension.stabilityValidation.outputDir) ignoredPackages.set(extension.stabilityValidation.ignoredPackages) @@ -212,6 +247,22 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { stabilityCheckTask, ) } + + // Add output parameter to the Kotlin tasks to ensure it is compatible with the Build Cache + target.tasks.withType(KotlinCompile::class.java) + .named { + it.contains(variantNameUpperCase) && isKotlinTaskApplicable( + it, + extension.stabilityValidation.includeTests.get(), + ) + } + .configureEach { + val stabilityDir = + target.layout.buildDirectory + .dir(getKotlinTaskStabilityFolderName(project, name)) + .get() + outputs.dir(stabilityDir).optional(true) + } } target.afterEvaluate { @@ -266,10 +317,19 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { return project.provider { val projectDependencies = collectProjectDependencies(project) + val stabilityFolderName = getKotlinTaskStabilityFolderName( + project, + kotlinCompilation.compileKotlinTaskName, + ) + // Write project dependencies to a file to avoid empty string issues with SubpluginOption - val stabilityDir = project.layout.buildDirectory.dir("stability").get().asFile + val stabilityDir = project.layout.buildDirectory.dir(stabilityFolderName).get().asFile stabilityDir.mkdirs() - val dependenciesFile = java.io.File(stabilityDir, "project-dependencies.txt") + + val variantlessStabilityDir = project.layout.buildDirectory.dir("stability").get().asFile + variantlessStabilityDir.mkdirs() + + val dependenciesFile = java.io.File(variantlessStabilityDir, "project-dependencies.txt") dependenciesFile.writeText(projectDependencies.joinToString("\n")) listOf( @@ -289,6 +349,32 @@ public class StabilityAnalyzerGradlePlugin : KotlinCompilerPluginSupportPlugin { } } + private fun getKotlinTaskStabilityFolderName( + project: Project, + taskName: String, + ): String { + val variant = + if (project.extensions.findByType(AndroidComponentsExtension::class.java) != null) { + taskName + .removePrefix("compile") + .removeSuffix("Kotlin") + .replaceFirstChar { it.lowercase() } + } else { + if (taskName.contains("Test")) { + "test" + } else { + "" + } + } + + val stabilityFolderName = if (variant.isBlank()) { + "stability" + } else { + "stability/$variant" + } + return stabilityFolderName + } + /** * Add runtime to compiler plugin classpath. * This ensures the compiler plugin can access runtime classes during compilation.