mcpField
V
validating-compose-stability

validating-compose-stability

github.com/skydoves测试与质量33958 次安装22 周前更新
代码仓库

关于 validating-compose-stability

面向 Jetpack Compose 团队的开发技能,通过已提交的基线和 Gradle 检查,在 CI 中验证 Composable 与参数的稳定性。它专注于发现回归,而非指导如何修复不稳定代码。

主要功能

  1. 01生成易读的 .stability 基线
  2. 02使用 Gradle 任务检查稳定性变化
  3. 03在 CI 中阻止稳定性回归
  4. 04排除指定包、类和项目
  5. 05支持本地仅警告模式

适用场景

  1. 01发现 Composable 变得不可跳过
  2. 02在拉取请求中审查稳定性变化
  3. 03在 Kotlin 编译后执行稳定性检查
  4. 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 式的执行顺序。

评论

支持 Markdown