ci: preserve frozen Android build contract (#105299)

This commit is contained in:
Peter Steinberger
2026-07-12 11:48:02 +01:00
committed by GitHub
parent ff4b4517d2
commit a20314d6cc
2 changed files with 71 additions and 6 deletions

View File

@@ -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

View File

@@ -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(