From a20314d6cc9f7538ad2b41d10dac842e2e2bfe01 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 12 Jul 2026 11:48:02 +0100 Subject: [PATCH] ci: preserve frozen Android build contract (#105299) --- .github/workflows/ci.yml | 17 ++++++- test/scripts/ci-workflow-guards.test.ts | 60 +++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 664652d45ee4..22ea987ab03c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -438,6 +438,8 @@ jobs: const targetWorkflow = existsSync(".github/workflows/ci.yml") ? readFileSync(".github/workflows/ci.yml", "utf8") : ""; + const supportsCurrentAndroidCi = targetWorkflow.includes("android-ci-contract-v2"); + const useCompatibleAndroidCi = compatibilityTarget && !supportsCurrentAndroidCi; const supportsFormatCheck = targetWorkflow.split("pnpm format:check").length - 1 >= 2; const runFormatCheck = !frozenTarget || supportsFormatCheck; @@ -548,10 +550,16 @@ jobs: android_matrix: createMatrix( runAndroid ? [ + // android-ci-contract-v2: both app variants, Android lint, benchmark, and ktlint. { check_name: "android-test-play", task: "test-play" }, { check_name: "android-test-third-party", task: "test-third-party" }, - { check_name: "android-build-play", task: "build-play" }, - { check_name: "android-ktlint", task: "ktlint" }, + { + check_name: "android-build-play", + task: useCompatibleAndroidCi ? "build-play-compat" : "build-play", + }, + ...(!useCompatibleAndroidCi + ? [{ check_name: "android-ktlint", task: "ktlint" }] + : []), ] : [], ), @@ -2545,6 +2553,11 @@ jobs: :app:lintThirdPartyDebug \ :benchmark:assembleDebug ;; + build-play-compat) + # Frozen targets keep their target-owned Android build contract. New lint rules + # must not retroactively reject a previously validated release branch. + ./gradlew --no-daemon --build-cache :app:assemblePlayDebug + ;; ktlint) # Mirrors `pnpm android:lint`; keeps formatting drift out of main (see PR #100304 sweep). ./gradlew --no-daemon --build-cache :app:ktlintCheck :benchmark:ktlintCheck diff --git a/test/scripts/ci-workflow-guards.test.ts b/test/scripts/ci-workflow-guards.test.ts index b17b2985f4ea..41e36667a541 100644 --- a/test/scripts/ci-workflow-guards.test.ts +++ b/test/scripts/ci-workflow-guards.test.ts @@ -59,6 +59,7 @@ function runCiManifestFixture(options: { historicalCompatibility?: boolean; iosCapabilities?: boolean; iosBuildCapability?: boolean; + androidCiCapabilities?: boolean; nativeI18nCapabilities?: boolean; protocolCoverage?: boolean; qaSmokePlan?: boolean; @@ -145,9 +146,14 @@ function runCiManifestFixture(options: { mkdirSync(path.dirname(targetWorkflow), { recursive: true }); writeFileSync( targetWorkflow, - (options.formatCheck ?? options.bundledPlanner) - ? "pnpm format:check\npnpm format:check\n" - : "", + [ + ...((options.formatCheck ?? options.bundledPlanner) + ? ["pnpm format:check", "pnpm format:check"] + : []), + ...((options.androidCiCapabilities ?? options.bundledPlanner) + ? ["android-ci-contract-v2"] + : []), + ].join("\n"), ); const outputPath = path.join(root, "manifest.out"); writeFileSync(outputPath, "", "utf8"); @@ -1263,7 +1269,8 @@ describe("ci workflow guards", () => { expect(source).toContain( '{ check_name: "android-test-third-party", task: "test-third-party" }', ); - expect(source).toContain('{ check_name: "android-build-play", task: "build-play" }'); + expect(source).toContain('check_name: "android-build-play"'); + expect(source).toContain('task: useCompatibleAndroidCi ? "build-play-compat" : "build-play"'); expect(runStep.run).toContain(":app:testPlayDebugUnitTest"); expect(runStep.run).toContain(":app:testThirdPartyDebugUnitTest"); expect(runStep.run).toContain(":app:assemblePlayDebug"); @@ -1705,6 +1712,12 @@ describe("ci workflow guards", () => { }); it("uses target-owned CI plans and capabilities for older release checkouts", () => { + const androidRun = readCiWorkflow().jobs.android.steps.find( + (step: WorkflowStep) => step.name === "Run Android ${{ matrix.task }}", + ).run; + expect(androidRun).toContain("build-play-compat)"); + expect(androidRun).toContain(":app:assemblePlayDebug"); + const legacy = runCiManifestFixture({ bundledPlanner: false }); expect(legacy.status, legacy.output).toBe(0); expect(legacy.outputs.historical_target).toBe("true"); @@ -1713,6 +1726,14 @@ describe("ci workflow guards", () => { expect(legacy.outputs.run_qa_smoke_ci).toBe("false"); expect(legacy.outputs.run_channel_contracts_shards).toBe("false"); expect(legacy.outputs.run_protocol_event_coverage).toBe("false"); + expect( + JSON.parse(expectDefined(legacy.outputs.android_matrix, "legacy Android matrix output")) + .include, + ).toEqual([ + { check_name: "android-test-play", task: "test-play" }, + { check_name: "android-test-third-party", task: "test-third-party" }, + { check_name: "android-build-play", task: "build-play-compat" }, + ]); expect( JSON.parse( expectDefined( @@ -1735,6 +1756,37 @@ describe("ci workflow guards", () => { expect(current.outputs.run_channel_contracts_shards).toBe("true"); expect(current.outputs.run_protocol_event_coverage).toBe("true"); expect(current.outputs.run_format_check).toBe("true"); + expect( + JSON.parse(expectDefined(current.outputs.android_matrix, "current Android matrix output")) + .include, + ).toEqual([ + { check_name: "android-test-play", task: "test-play" }, + { check_name: "android-test-third-party", task: "test-third-party" }, + { check_name: "android-build-play", task: "build-play" }, + { check_name: "android-ktlint", task: "ktlint" }, + ]); + + const currentMissingAndroidCapabilities = runCiManifestFixture({ + androidCiCapabilities: false, + bundledPlanner: true, + eventName: "pull_request", + }); + expect(currentMissingAndroidCapabilities.status, currentMissingAndroidCapabilities.output).toBe( + 0, + ); + expect( + JSON.parse( + expectDefined( + currentMissingAndroidCapabilities.outputs.android_matrix, + "current fallback-resistant Android matrix output", + ), + ).include, + ).toEqual([ + { check_name: "android-test-play", task: "test-play" }, + { check_name: "android-test-third-party", task: "test-third-party" }, + { check_name: "android-build-play", task: "build-play" }, + { check_name: "android-ktlint", task: "ktlint" }, + ]); expect( JSON.parse( expectDefined(