关于 validating-compose-stability
面向 Jetpack Compose 团队的开发技能,通过已提交的基线和 Gradle 检查,在 CI 中验证 Composable 与参数的稳定性。它专注于发现回归,而非指导如何修复不稳定代码。
主要功能
- 01生成易读的 .stability 基线
- 02使用 Gradle 任务检查稳定性变化
- 03在 CI 中阻止稳定性回归
- 04排除指定包、类和项目
- 05支持本地仅警告模式
适用场景
- 01发现 Composable 变得不可跳过
- 02在拉取请求中审查稳定性变化
- 03在 Kotlin 编译后执行稳定性检查
- 04将预览函数排除在基线之外
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ skydoves/ android-testing-skills --skill validating-compose-stability安装后在 Claude Code 中输入/validating-compose-stability确认已加载。
详细介绍
验证 Compose 稳定性——针对可跳过性和参数稳定性的 CI 门禁
Compose 稳定性——即一个可组合项是否可跳过/可重启,以及它的参数是否稳定——在重组分析器于生产环境中发现问题之前都是不可见的。本技能将其设为构建门禁:把每个可组合项当前的稳定性快照保存到已提交的 .stability 基线中,并在更改导致任何内容变得不那么稳定时,让 CI 检查失败。使用的工具是 Compose Stability Analyzer Gradle 插件。(关于如何编写稳定的可组合项——@Immutable/@Stable、编译器配置文件、延迟读取——请参阅 compose-performance-skills 仓库;本技能只负责验证/CI 门禁。)
何时使用此技能
- 用户希望在可组合项变得不可跳过,或某个参数从稳定变为不稳定时,让
./gradlew stabilityCheck使 CI 检查失败。 - 用户希望提交一份可供审查的、涵盖所有可组合项稳定性的基线(
.stability文件),以便在 PR 差异中查看更改。 - 某次重组回归问题进入了发布版本,因为没有任何检查标记出热路径可组合项新增的
List参数或非@Immutable数据类。 - 用户提到
stabilityDump、stabilityCheck、composeStabilityAnalyzer、@IgnoreStabilityReport或“stability baseline”。 - 用户希望本地只发出警告,而 CI 中则严格失败。
何时不使用此技能
- 用户希望修复不稳定参数(
@Immutable/@Stable、kotlinx.collections.immutable、用于外部类型的stability_config.conf、延迟读取状态、derivedStateOf)——这属于 Compose 性能编写实践;请参阅compose-performance-skills仓库中的稳定性技能。本技能只负责验证和设门禁。 - 用户希望查找运行时哪些重组正在触发(Layout Inspector 重组计数、
Recomposer指标、组合跟踪)——这是不同的工具,在运行时而非构建时使用。 - 用户正在进行 UI 行为测试或截图测试——请参阅
../../setup/choosing-test-rule-vs-runtest/SKILL.md和../../preview/capturing-preview-screenshots-in-ci/SKILL.md。
前置条件
- 每个 Compose 模块都应用了 Compose Stability Analyzer Gradle 插件(通过 plugins 块应用;坐标和版本取自项目的目录/插件文档)。
- Compose 编译器指标可用——该插件会分析编译后的输出,因此必须先运行模块的 Kotlin 编译,再运行
stabilityCheck/stabilityDump。在 CI 中,这意味着设置needs: build(或先运行:module:compileDebugKotlin)——在编译前运行检查会产生错误结果或失败。 - 已将基线提交到版本控制:
<module>/stability/<module>.stability(由stabilityDump生成)。
工作流
- 1. 配置验证区块。 在每个 Compose 模块的
build.gradle.kts中:
composeStabilityAnalyzer {
stabilityValidation {
enabled.set(true)
outputDir.set(layout.projectDirectory.dir("stability")) // where the .stability baseline lives
includeTests.set(false)
ignoredPackages.set(listOf("com.example.internal"))
ignoredClasses.set(listOf("PreviewComposables"))
ignoredProjects.set(listOf("benchmarks", "examples"))
failOnStabilityChange.set(true) // build fails on stability changes; false => warning-only
ignoreNonRegressiveChanges.set(false) // true => only regressions count; new-stable / improvements ignored
allowMissingBaseline.set(false) // true => no baseline yet is not a failure (bootstrap only)
stabilityConfigurationFiles.add( // same format as the Compose compiler's stability config
rootProject.layout.projectDirectory.file("stability_config.conf")
)
}
}
关键选项:failOnStabilityChange(门禁)、ignoreNonRegressiveChanges(仅报告退化,忽略改进和新增的稳定可组合项)、stabilityConfigurationFiles(声明外部类型为稳定的 Compose 编译器格式文件),以及用于排除契约范围外的包、类和模块的 ignored* 列表。
- 2. 生成一次基线并提交。 模块编译完成后:
./gradlew :app:compileDebugKotlin
./gradlew :app:stabilityDump # writes app/stability/app.stability
git add app/stability/app.stability
git commit -m "Add Compose stability baseline"
.stability 文件便于阅读:它会列出每个可组合项的完全限定签名、可跳过/可重启状态,以及每个参数的稳定性分类和原因。Android 项目会生成特定变体的任务(debugStabilityDump、releaseStabilityCheck,等等);多模块项目则每个模块各有一个 .stability 文件——./gradlew stabilityDump 会运行所有任务,也可以指定模块。
- 3. 在 CI 中于编译后运行检查。
stabilityCheck会将当前编译结果与已提交的基线进行比较,并报告三种变化:~稳定性退化(可组合项/参数变得不那么稳定)、+新增可组合项、-移除可组合项。发生退化时,构建会失败,并显示受影响的可组合项及其稳定性变化,同时提示如果该变化是有意为之,请运行stabilityDump。
# .github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '21', distribution: 'zulu' }
- run: ./gradlew :app:compileDebugKotlin
stability_check:
name: Compose Stability Check
runs-on: ubuntu-latest
needs: build # MANDATORY — the analysis reads compiled output
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { java-version: '21', distribution: 'zulu' }
- run: ./gradlew stabilityCheck
- 4. 谨慎接受有意的稳定性变化。 如果确实发生了退化且已接受(例如,有充分理由使用
List参数,或某个外部类型确实无法设为稳定类型),请更新基线,并将其作为附有说明的提交——绝不要悄悄更新:
./gradlew :app:compileDebugKotlin
./gradlew :app:stabilityDump
git add app/stability/app.stability
git commit -m "Update stability baseline: PokemonList now takes List<Pokemon> — justified by [reason]"
.stability 文件中的差异是审查依据;提交说明则是审计记录。
- 5. 排除不属于契约范围的内容。 使用
@IgnoreStabilityReport标注不应纳入跟踪的可组合项——例如@Preview函数、内部辅助代码;它们会同时从转储和检查中排除。对于整个包、类或模块,请使用ignoredPackages/ignoredClasses/ignoredProjects。
@IgnoreStabilityReport
@Preview
@Composable
fun UserCardPreview() { UserCard(user = User("John", 30)) }
- 6.(可选)CI 中严格检查,本地仅警告。 在 CI 服务上严格执行门禁,在开发者机器上则不阻塞:
composeStabilityAnalyzer {
stabilityValidation { failOnStabilityChange.set(System.getenv("CI") == "true") }
}
大多数 CI 平台都会设置 CI=true。
模式
模式:在编译前运行 stabilityCheck
# WRONG
stability_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./gradlew stabilityCheck # no compiled output yet
# WRONG because: the analyzer reads the Compose compiler's output. With nothing compiled it
# either fails or reports incorrect results. The check must depend on a build step.
# RIGHT
stability_check:
runs-on: ubuntu-latest
needs: build # or: - run: ./gradlew :app:compileDebugKotlin stabilityCheck
steps:
- uses: actions/checkout@v4
- run: ./gradlew stabilityCheck
模式:重新生成基线以“让 CI 通过”
# WRONG — CI failed on a stability regression, so:
./gradlew stabilityDump && git commit -am "fix stability check"
# WRONG because: stabilityDump rewrites the baseline to whatever the code currently produces, so
# the check trivially passes — the regression ships, undocumented. The dump is for *accepting* a
# change you understand, with a commit message that says why; it is not a way to silence the gate.
# RIGHT — investigate the ~ entries first; only then, if the change is justified:
./gradlew :app:compileDebugKotlin :app:stabilityDump
git add app/stability/app.stability
git commit -m "Update stability baseline: <composable> <what changed> — justified by <reason>"
模式:@Preview 函数污染基线
// WRONG — preview functions in the .stability baseline, churning it on every preview edit
@Preview @Composable fun ProfilePreview() { Profile(sampleUser) }
// WRONG because: previews are tooling entry points, not API. Tracking them means the baseline
// diffs every time someone tweaks a preview, drowning real regressions.
// RIGHT — annotate them out (or ignoredClasses = listOf("...Preview...") for a naming convention)
@IgnoreStabilityReport @Preview @Composable fun ProfilePreview() { Profile(sampleUser) }
强制规则
- 必须将生成的
.stability基线文件提交到版本控制中——它们是共享契约;未提交的基线无法发挥门禁作用。 - 必须仅在 Kotlin 编译后运行
stabilityCheck(CI:通过needs:依赖构建任务,或在同一次 Gradle 调用中执行编译)。分析器会读取编译产物。 - 必须将
stabilityDump基线更新视为有意且经过审查的提交,并在提交说明中给出理由——绝不能用它来消除检查失败。 - 必须通过
@IgnoreStabilityReport(或ignoredClasses/ignoredPackages)排除@Preview和其他非 API 可组合项,以确保基线差异只反映真实变化。 - 不得永久设置
failOnStabilityChange = false(或allowMissingBaseline = true)——这些设置仅用于初始配置;基线建立后,门禁必须在发生退化时失败。 - 不得把此技能用作修复稳定性的地方——稳定性修复属于 Compose 编写工作(
@Immutable/@Stable、不可变集合、编译器稳定性配置);请参阅compose-performance-skills仓库。 - **首选:**如果团队只关心退化,并觉得
+/新增稳定项的提示会分散注意力,则设置ignoreNonRegressiveChanges = true。 - **首选:**CI 中严格检查,本地仅警告,可通过
failOnStabilityChange.set(System.getenv("CI") == "true")实现。
验证
-
./gradlew stabilityDump会生成<module>/stability/<module>.stability,并且该文件已提交(执行无变更的 dump 后,git status状态干净)。 - 对已提交的基线执行
./gradlew stabilityCheck会通过;当故意让某个可组合项变得不稳定时(例如,为受跟踪的可组合项添加List<T>参数),检查会失败,并显示其名称和一个~条目。 - CI 工作流的稳定性任务设置了
needs: build(或会在stabilityCheck前编译)。 -
@Preview/ 脚手架可组合项带有@IgnoreStabilityReport(或匹配ignoredClasses模式),且不会出现在.stability文件中。 - 基线存在后,
failOnStabilityChange为true(或通过CI控制为true),且allowMissingBaseline为false。
参考资料
- skydoves.github.io/compose-stability-analyzer/gradle-plugin/stability-validation/ —
stabilityDump/stabilityCheck任务、composeStabilityAnalyzer { stabilityValidation { … } }DSL 及所有选项、.stability基线格式、~/+/-变更类型、@IgnoreStabilityReport、多模块行为。 - skydoves.github.io/compose-stability-analyzer/gradle-plugin/ci-cd/ — GitHub Actions 配置、必需的
needs: build依赖、构建失败行为、基线更新工作流、failOnStabilityChange.set(System.getenv("CI") == "true")。 - github.com/skydoves/compose-stability-analyzer — 插件源码、坐标及完整配置参考。
- developer.android.com/develop/ui/compose/performance/stability — Compose 稳定性概念(可跳过/可重启、稳定参数),基线以此为基础。
- developer.android.com/develop/ui/compose/performance/stability/fix — 修复不稳定性(
@Immutable/@Stable、不可变集合、编译器的stability_configuration_path文件)——此关卡保护的编写环节。 - 同级技能:
../../preview/capturing-preview-screenshots-in-ci/SKILL.md— 另一项 Compose CI 关卡(设备渲染的预览目录);同样需要关注needs: build式的执行顺序。