Skip to content

Store stability output of Android variants to their own folder#150

Open
matejdro wants to merge 4 commits intoskydoves:mainfrom
matejdro:android-variants-separate-folder
Open

Store stability output of Android variants to their own folder#150
matejdro wants to merge 4 commits intoskydoves:mainfrom
matejdro:android-variants-separate-folder

Conversation

@matejdro
Copy link
Copy Markdown
Contributor

@matejdro matejdro commented Apr 13, 2026

🎯 Goal

A continuation of the #101

Up until now, every Kotlin task dropped its stability output into build/stability. This was problematic with multi-variant runs:

  1. If debug and release source sets are different, stability output might be different, but they would overwrite each other
  2. If debug and release compile was ran in parallel, two tasks might write to this file simultaneously, corrupting it

PR fixes this by changing plugin to store stability output into build/stability/$VARIANT (such as /build/stability/debug)

Summary by CodeRabbit

  • Refactor
    • Improved build-cache behavior by wiring Kotlin compile outputs only for applicable tasks and making plugin output locations task/variant-aware.
    • Stability inputs/outputs are now organized per Android build variant; non-Android builds include shared and test-scoped stability data.
    • Project dependency listing remains consolidated in a single, variant-independent location.
  • New Features
    • IDE toolwindow now aggregates stability data from per-variant folders plus the root stability folder.

@matejdro matejdro requested a review from skydoves as a code owner April 13, 2026 12:35
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Walkthrough

The Gradle plugin and IDEA collector were made variant/task-aware: Kotlin compile tasks are now configured per-task or per-variant, stability plugin output/input paths are mapped to task/variant-scoped build/stability/... folders, and the IDEA collector enumerates and parses stability-info.json from the root build/stability and its immediate subfolders.

Changes

Cohort / File(s) Summary
Gradle plugin variant-aware wiring
stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt
Removed unconditional KotlinCompile registration. Configure only applicable Kotlin tasks (non-Android: isKotlinTaskApplicable; Android: tasks matching variant name). Mark task-derived stability output directories as outputs.dir(...).optional(true). Add variant-scoped inputs for dump/check tasks and make compiler-plugin OPTION_STABILITY_OUTPUT_DIR task/variant-aware while keeping build/stability/project-dependencies.txt in the variantless root. Added helper getKotlinTaskStabilityFolderName(project, taskName).
IDEA toolwindow collector — per-variant discovery
compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt
Switch discovery to treat build/stability as root, enumerate immediate subdirectories as variant folders, and parse stability-info.json from root plus each variant folder. Parsing logic unchanged but now executed per-folder; error handling scope adjusted accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Gradle as Gradle Plugin
    participant Android as Android Variant System
    participant Kotlin as KotlinCompile Task
    participant Compiler as Kotlin Compiler Plugin
    participant FS as Filesystem (build/stability)
    participant IDEA as IDEA Collector

    Gradle->>Android: query variants (if Android project)
    alt Android project
        Android->>Gradle: provide variant names
        Gradle->>Kotlin: locate KotlinCompile tasks matching variant names
        Kotlin->>Compiler: set OPTION_STABILITY_OUTPUT_DIR=build/stability/<variant>
        Kotlin->>FS: mark build/stability/<variant> as optional output
    else Non-Android
        Gradle->>Kotlin: locate applicable KotlinCompile tasks
        Kotlin->>Compiler: set OPTION_STABILITY_OUTPUT_DIR=build/stability[/test]
        Kotlin->>FS: mark build/stability[/test] as optional output
    end
    Compiler->>FS: write stability-info.json (variant/task-scoped) and project-dependencies.txt (build/stability/project-dependencies.txt)
    IDEA->>FS: enumerate build/stability and immediate subfolders
    IDEA->>FS: read stability-info.json files and parse composable data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

IntelliJ Plugin, Runtime Analyzer, Stability Validation

Suggested reviewers

  • skydoves

Poem

🐰 I hop through variant folders, one by one,
Leaving tiny crumbs where stability's done.
Compiler and tasks each find their own nest,
Collector reads them, then hops off to rest.
Builds stay steady — a rabbit's little quest.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description includes the Goal section explaining the problem and solution, but lacks Implementation details, code examples, and formatting/API validation instructions from the template. Add Implementation details section explaining the technical approach, provide code examples, and include instructions for running spotlessApply and apiDump before review.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: storing Android variant stability output in separate folders instead of a shared directory.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt`:
- Around line 291-295: The IDE tool window still only reads the flat path
"build/stability/stability-info.json" so after StabilityAnalyzerGradlePlugin.kt
started writing variant outputs into "stability/<variant>/stability-info.json"
the collector stops seeing Android results; update ComposableStabilityCollector
(class/method that scans build/stability) to either (a) enumerate the
build/stability directory and read stability-info.json from every subdirectory
(e.g., stability/<variant>/stability-info.json) or (b) fall back to the legacy
root file if present—locate the scan logic around the method that currently
constructs the path "build/stability/stability-info.json" and change it to walk
child directories or attempt both locations so both legacy and variant outputs
are discovered.
- Around line 129-135: The KotlinCompile task filters currently hardcode false
in the calls to isKotlinTaskApplicable, causing test compile tasks to be treated
as non-writing when stabilityValidation.includeTests is enabled; change those
calls so they pass the actual includeTests flag (the same boolean used for
stabilityValidation) instead of false in the
target.tasks.withType(KotlinCompile::class.java).named {
isKotlinTaskApplicable(it, false) } blocks (both the block that sets
outputs.dir(stabilityDir).optional(true) and the similar block at the later
location), so isKotlinTaskApplicable receives the real includeTests value and
test compile tasks correctly declare their build/stability outputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c30514d1-dc51-40cc-8ad8-c5204ecc728a

📥 Commits

Reviewing files that changed from the base of the PR and between 87fcf75 and 8611989.

📒 Files selected for processing (1)
  • stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt

Comment on lines +291 to +295
val stabilityFolderName = if (variant.isBlank()) {
"stability"
} else {
"stability/$variant"
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Update the IDEA collector before moving Android outputs.

compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt lines 60-65 still only scan build/stability/stability-info.json. After this change, Android builds emit build/stability/<variant>/stability-info.json, so the tool window will stop discovering fresh Android results unless it also walks the variant subdirectories or you keep a compatibility file in the root folder.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt`
around lines 291 - 295, The IDE tool window still only reads the flat path
"build/stability/stability-info.json" so after StabilityAnalyzerGradlePlugin.kt
started writing variant outputs into "stability/<variant>/stability-info.json"
the collector stops seeing Android results; update ComposableStabilityCollector
(class/method that scans build/stability) to either (a) enumerate the
build/stability directory and read stability-info.json from every subdirectory
(e.g., stability/<variant>/stability-info.json) or (b) fall back to the legacy
root file if present—locate the scan logic around the method that currently
constructs the path "build/stability/stability-info.json" and change it to walk
child directories or attempt both locations so both legacy and variant outputs
are discovered.

✅ Addressed in commit b61d18a

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm that's a good point, it would break IDEA plugin. @skydoves how should we handle this? Would we add a variant property to the idea plugin?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I think we could do is, have Intellij plugin scan the stability folder + all the subfolders for the stability-info.json. That way it detects the json file regardless of the variant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added check-all-folders, however it is untested. I'm not sure how can I build the IntelliJ plugin, it does not appear to be documented. Or did I miss it?

@matejdro matejdro force-pushed the android-variants-separate-folder branch from 8611989 to b61d18a Compare April 13, 2026 12:42
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt`:
- Around line 125-126: In ComposableStabilityCollector.kt, the catch (e:
Exception) that swallows parse failures for stability-info.json should log the
failing folder path and exception instead of ignoring it; update the catch to
call the module/class logger (or Logger.getInstance(...).error) including the
folder or file path being parsed and e (stacktrace/message) so users can
distinguish "no output" from "failed to read/generated" when stability-info.json
parsing fails.
- Around line 63-64: The collector currently flattens variantFolders +
rootStabilityFolder into allStabilityFolders and loses variant identity, causing
duplicate composables to be indistinguishable and StabilityStats to overcount;
update ComposableStabilityCollector to either (A) carry variant metadata with
each folder entry (e.g., change the collection element to a data class or pair
like (variantName, folder) and propagate that through the map/reduce that builds
StabilityStats and the UI model) or (B) deduplicate exact composable entries
before computing the summary (compare by canonical path/signature and collapse
identical entries) and apply the same change to the other aggregation sites
around the blocks that build the stats and UI model so counts reflect unique
composables per variant or globally as intended.
- Around line 61-64: The loop currently builds allStabilityFolders from
rootStabilityFolder ("build/stability") only, so non-Android test outputs
written to "build/stabilityTest/stability-info.json" are missed; update the
collector to also consider "build/stabilityTest" (e.g., create a
rootStabilityTest = File(contentRoot.path, "build/stabilityTest") and include
its directory plus its variant children into allStabilityFolders), and only
add/scan that folder when the includeTests flag/property is enabled; keep
references to rootStabilityFolder, rootStabilityTest, variantFolders,
allStabilityFolders, and the existing folder loop so the new folder is treated
the same as the original.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b3523a1-aec5-4bfc-aa92-01021269f4ba

📥 Commits

Reviewing files that changed from the base of the PR and between b61d18a and 5541142.

📒 Files selected for processing (2)
  • compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt
  • stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt

Comment on lines +63 to +64
val allStabilityFolders = variantFolders + rootStabilityFolder
for (folder in allStabilityFolders) {
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve variant identity or dedupe before building stats.

The collector now merges debug, release, *UnitTest, etc. into one flat list but throws away the folder/variant name. Shared composables will show up as indistinguishable duplicates, and StabilityStats will overcount them. Please either carry the variant through the model/UI or collapse exact duplicates before computing the summary.

Also applies to: 110-123, 133-136

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@compose-stability-analyzer-idea/src/main/kotlin/com/skydoves/compose/stability/idea/toolwindow/ComposableStabilityCollector.kt`
around lines 63 - 64, The collector currently flattens variantFolders +
rootStabilityFolder into allStabilityFolders and loses variant identity, causing
duplicate composables to be indistinguishable and StabilityStats to overcount;
update ComposableStabilityCollector to either (A) carry variant metadata with
each folder entry (e.g., change the collection element to a data class or pair
like (variantName, folder) and propagate that through the map/reduce that builds
StabilityStats and the UI model) or (B) deduplicate exact composable entries
before computing the summary (compare by canonical path/signature and collapse
identical entries) and apply the same change to the other aggregation sites
around the blocks that build the stats and UI model so counts reflect unique
composables per variant or globally as intended.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a good point, but I feel extra UI is out of scope for this PR. What do you think @skydoves?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt`:
- Around line 87-92: The code uses stabilityInputFiles.setFrom(...) twice which
causes the second call to overwrite the first; change the second invocation to
stabilityInputFiles.from(target.layout.buildDirectory.file("stability/test/stability-info.json"))
so the test file is appended instead of replacing the initial
stability/stability-info.json entry, ensuring stabilityInputFiles contains both
files.
- Around line 175-187: The stabilityInputFiles property is being overwritten by
repeated setFrom() calls so only the last file (AndroidTest) is kept; update the
code that sets stabilityInputFiles (references: stabilityInputFiles and
variant.name in StabilityAnalyzerGradlePlugin.kt) to aggregate all three files
instead of replacing them—use a single setFrom(...) with a collection of files
or call from(...) / addAll(...) for each file (e.g., gather the three
target.layout.buildDirectory.file(...) results into a list and pass that to
stabilityInputFiles.setFrom or call stabilityInputFiles.from(...) for each) so
main variant, UnitTest and AndroidTest files are all included.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d43cb7f-227f-42bc-bdd4-4819296aee53

📥 Commits

Reviewing files that changed from the base of the PR and between 5541142 and 56b51f0.

📒 Files selected for processing (1)
  • stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt

Comment on lines 87 to +92
stabilityInputFiles.setFrom(
target.layout.buildDirectory.file("stability/stability-info.json"),
)
stabilityInputFiles.setFrom(
target.layout.buildDirectory.file("stability/test/stability-info.json"),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Second setFrom() overwrites the first input file.

setFrom() replaces the entire file collection. The call at line 90 discards the file added at line 87, so the dump task will only read from stability/test/stability-info.json. Use from() for the second call to append instead.

🐛 Proposed fix
       stabilityInputFiles.setFrom(
         target.layout.buildDirectory.file("stability/stability-info.json"),
       )
-      stabilityInputFiles.setFrom(
+      stabilityInputFiles.from(
         target.layout.buildDirectory.file("stability/test/stability-info.json"),
       )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt`
around lines 87 - 92, The code uses stabilityInputFiles.setFrom(...) twice which
causes the second call to overwrite the first; change the second invocation to
stabilityInputFiles.from(target.layout.buildDirectory.file("stability/test/stability-info.json"))
so the test file is appended instead of replacing the initial
stability/stability-info.json entry, ensuring stabilityInputFiles contains both
files.

Comment on lines 175 to 187
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",
),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Repeated setFrom() calls overwrite previous input files.

Same issue as the non-Android path—each setFrom() replaces the collection, so only the AndroidTest file will be read. The main variant and UnitTest files are discarded.

🐛 Proposed fix
       stabilityInputFiles.setFrom(
         target.layout.buildDirectory.file("stability/${variant.name}/stability-info.json"),
       )
-      stabilityInputFiles.setFrom(
+      stabilityInputFiles.from(
         target.layout.buildDirectory.file(
           "stability/${variant.name}UnitTest/stability-info.json",
         ),
       )
-      stabilityInputFiles.setFrom(
+      stabilityInputFiles.from(
         target.layout.buildDirectory.file(
           "stability/${variant.name}AndroidTest/stability-info.json",
         ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@stability-gradle/src/main/kotlin/com/skydoves/compose/stability/gradle/StabilityAnalyzerGradlePlugin.kt`
around lines 175 - 187, The stabilityInputFiles property is being overwritten by
repeated setFrom() calls so only the last file (AndroidTest) is kept; update the
code that sets stabilityInputFiles (references: stabilityInputFiles and
variant.name in StabilityAnalyzerGradlePlugin.kt) to aggregate all three files
instead of replacing them—use a single setFrom(...) with a collection of files
or call from(...) / addAll(...) for each file (e.g., gather the three
target.layout.buildDirectory.file(...) results into a list and pass that to
stabilityInputFiles.setFrom or call stabilityInputFiles.from(...) for each) so
main variant, UnitTest and AndroidTest files are all included.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant