FN-7209: require JDK 21 for Android CI builds

Align Android Gradle build environments with Capacitor's Java 21 source compatibility.

- Update mobile, release, and test-release workflows to provision Temurin JDK 21.
- Document Java 21 as the required Android Gradle build JDK.
- Add CI workflow coverage that checks Android build jobs satisfy @capacitor/android sourceCompatibility.

Files changed:
 .github/workflows/mobile.yml                   |  6 +-
 .github/workflows/release.yml                  |  6 +-
 .github/workflows/test-release.yml             |  6 +-
 MOBILE.md                                      |  4 +-
 packages/cli/src/__tests__/ci-workflow.test.ts | 99 +++++++++++++++++++++++++-
 5 files changed, 112 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-7209

Fusion-Task-Lineage: d9e50bee-d7f8-4e87-b33e-136ffffc93af

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-06-28 16:56:35 -07:00
parent 8eed09c126
commit bc38f0c842
5 changed files with 112 additions and 9 deletions

View File

@@ -111,11 +111,13 @@ jobs:
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Setup Java 17
# FNXC:MobileAndroidBuild 2026-06-28-00:00:
# Capacitor 7 @capacitor/android compiles its Android library with JavaVersion.VERSION_21; manual Android Gradle builds must provision JDK 21 because JDK 17 fails with `invalid source release: 21`.
- name: Setup Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
java-version: "21"
- name: Cache Android Gradle caches
uses: actions/cache@v4

View File

@@ -389,11 +389,13 @@ jobs:
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Setup Java 17
# FNXC:MobileAndroidBuild 2026-06-28-00:00:
# Capacitor 7 @capacitor/android compiles its Android library with JavaVersion.VERSION_21; release APK/AAB Gradle builds must provision JDK 21 because JDK 17 fails with `invalid source release: 21`.
- name: Setup Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
java-version: "21"
- name: Cache Android Gradle caches
uses: actions/cache@v4

View File

@@ -375,11 +375,13 @@ jobs:
- name: Setup Node and install dependencies
uses: ./.github/actions/setup-node-pnpm
- name: Setup Java 17
# FNXC:MobileAndroidBuild 2026-06-28-00:00:
# Capacitor 7 @capacitor/android compiles its Android library with JavaVersion.VERSION_21; rehearsal APK/AAB Gradle builds must provision JDK 21 because JDK 17 fails with `invalid source release: 21`.
- name: Setup Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
java-version: "21"
- name: Cache Android Gradle caches
uses: actions/cache@v4

View File

@@ -8,7 +8,7 @@ Fusion mobile builds package the dashboard web client into Capacitor shells via
- **pnpm** 10+
- **Xcode** (iOS builds)
- **Android Studio** (Android SDK + emulator tooling)
- **Java JDK** 17+ (Android Gradle builds)
- **Java JDK** 21+ (Android Gradle builds)
## Quick Start
@@ -160,7 +160,7 @@ You can also use ImageMagick if preferred.
### Android build fails
- Verify Java 17+ (`java -version`)
- Verify Java 21+ (`java -version`)
- Confirm Android SDK and Gradle tooling are installed via Android Studio
### PWA does not install

View File

@@ -1,5 +1,5 @@
import { describe, it, expect, beforeAll } from "vitest";
import { readFileSync, accessSync, constants } from "node:fs";
import { readFileSync, accessSync, constants, existsSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { parse } from "yaml";
@@ -27,6 +27,84 @@ function findCompositeSetupStep(steps: any[]) {
return steps.find((step) => step.uses === "./.github/actions/setup-node-pnpm");
}
function findSetupJavaStep(steps: any[]) {
return steps.find((step) => typeof step.uses === "string" && step.uses.startsWith("actions/setup-java@"));
}
function resolveCapacitorAndroidGradlePath(): string {
const primaryPath = join(
workspaceRoot,
"packages",
"mobile",
"node_modules",
"@capacitor",
"android",
"capacitor",
"build.gradle",
);
if (existsSync(primaryPath)) {
return primaryPath;
}
const pnpmStoreCandidates = [
join(workspaceRoot, "node_modules", ".pnpm"),
join(workspaceRoot, "packages", "mobile", "node_modules", ".pnpm"),
];
for (const pnpmStore of pnpmStoreCandidates) {
if (!existsSync(pnpmStore)) {
continue;
}
for (const entry of readdirSync(pnpmStore)) {
if (!entry.startsWith("@capacitor+android@")) {
continue;
}
const candidate = join(
pnpmStore,
entry,
"node_modules",
"@capacitor",
"android",
"capacitor",
"build.gradle",
);
if (existsSync(candidate)) {
return candidate;
}
}
}
throw new Error(
"Unable to resolve @capacitor/android capacitor/build.gradle from packages/mobile/node_modules or the pnpm store",
);
}
function readCapacitorAndroidSourceCompatibilityMajor(): number {
const gradleContent = readFileSync(resolveCapacitorAndroidGradlePath(), "utf-8");
const match = gradleContent.match(/sourceCompatibility\s+JavaVersion\.VERSION_(\d+)/);
if (!match) {
throw new Error("Unable to parse @capacitor/android sourceCompatibility JavaVersion.VERSION_<major>");
}
return Number.parseInt(match[1], 10);
}
function expectAndroidBuildJobJavaVersionAtLeast(workflow: any, workflowName: string, minimumJavaVersion: number) {
const buildAndroidJob = workflow.jobs?.["build-android"];
expect(buildAndroidJob, `${workflowName} must define a build-android job`).toBeDefined();
const setupJavaStep = findSetupJavaStep(buildAndroidJob?.steps ?? []);
expect(setupJavaStep, `${workflowName} build-android must provision Java`).toBeDefined();
expect(setupJavaStep.name).toBe("Setup Java 21");
const javaVersion = setupJavaStep.with?.["java-version"];
expect(javaVersion, `${workflowName} build-android must pin JDK 21`).toBe("21");
expect(Number.parseInt(javaVersion, 10), `${workflowName} JDK must satisfy @capacitor/android sourceCompatibility`).toBeGreaterThanOrEqual(
minimumJavaVersion,
);
}
describe("Merge gate (.github/workflows/pr-checks.yml)", () => {
let workflow: any;
let content: string;
@@ -342,6 +420,25 @@ describe("Version & Release workflow (.github/workflows/version.yml)", () => {
});
});
describe("Android build JDK compatibility", () => {
let capacitorAndroidSourceCompatibility: number;
beforeAll(() => {
capacitorAndroidSourceCompatibility = readCapacitorAndroidSourceCompatibilityMajor();
});
/*
FNXC:MobileAndroidBuild 2026-06-28-00:00:
Android CI jobs that run Capacitor Gradle builds must provision a JDK version greater than or equal to @capacitor/android's sourceCompatibility. FN-7209 proved JDK 17 silently drifted below Capacitor 7's JavaVersion.VERSION_21 requirement and failed release builds with `invalid source release: 21`.
*/
it("pins every Android-building workflow to a JDK compatible with Capacitor", () => {
for (const workflowName of ["release.yml", "test-release.yml", "mobile.yml"]) {
const workflow = loadWorkflow(workflowName).parsed;
expectAndroidBuildJobJavaVersionAtLeast(workflow, workflowName, capacitorAndroidSourceCompatibility);
}
});
});
describe("Binary release workflow (.github/workflows/release.yml)", () => {
let workflow: any;
let content: string;