ionic-team / ionic-team/capacitor

[Bug]: `cap build android` fails with cryptic `MinSdkVersionException` / `Missing AndroidManifest.xml` when using `apksigner` with AAB (related to #6909)

Open
#8,428 0 comments 0 reactions 0 assignees View on GitHub
triage
Dominant language
TypeScript
Stars
16.7k
Forks
1.3k
Avg merge
3d 15h
Merged PRs (30d)
10

Description

### Capacitor Version

💊 Capacitor Doctor 💊

Latest Dependencies:

@capacitor/cli: 8.3.0
@capacitor/core: 8.3.0
@capacitor/android: 8.3.0
@capacitor/ios: 8.3.0

Installed Dependencies:

@capacitor/ios: not installed
@capacitor/android: 8.1.0
@capacitor/cli: 8.1.0
@capacitor/core: 8.1.0

[success] Android looking great! 👌

### Other API Details

```Shell
- Capacitor CLI version: (any version with `signingType` support, i.e. ≥ 5.1.0)
- Platform: Android
- `releaseType`: `'AAB'` (default)
- `signingType`: `'apksigner'`
```

### Platforms Affected

- [ ] iOS
- [x] Android
- [ ] Web

### Current Behavior

**Describe the bug**

When `signingType` is set to `'apksigner'` and `releaseType` is `'AAB'`, `npx cap build android` fails during the signing step with a confusing Java stack trace. The root cause is that `apksigner` does not support the AAB format — it is an APK-only tool — but the CLI passes the `.aab` file to it without any validation.

**Error output**

```
✖ Signing Release - failed!
[error] Exception in thread "main" com.android.apksig.apk.MinSdkVersionException: Failed to determine APK's minimum supported platform version. Use --min-sdk-version to override
at com.android.apksigner.ApkSignerTool.sign(ApkSignerTool.java:431)
at com.android.apksigner.ApkSignerTool.main(ApkSignerTool.java:94)
Caused by: com.android.apksig.apk.MinSdkVersionException: Failed to determine APK's minimum supported Android platform version
at com.android.apksig.ApkSigner.getMinSdkVersionFromApk(ApkSigner.java:1019)
at com.android.apksig.ApkSigner.sign(ApkSigner.java:301)
...
Caused by: com.android.apksig.apk.ApkFormatException: Missing AndroidManifest.xml
at com.android.apksig.ApkSigner.getAndroidManifestFromApk(ApkSigner.java:975)
...
```

**Steps to reproduce**

1. Set `signingType: 'apksigner'` in `capacitor.config.ts` (or pass `--signing-type apksigner` on the CLI), while `releaseType` is `'AAB'` (the default, or explicitly set).
2. Run `npx cap build android --keystorepath ... --keystorepass ...`
3. The Gradle build succeeds, but the signing step fails with the above error.

### Additional context

The inverse problem also exists. The default `signingType` is `'jarsigner'`:

```typescript
// cli/src/tasks/build.ts, line 49
signingtype: buildOptions.signingtype || config.android.buildOptions.signingType || 'jarsigner',
```

`jarsigner` uses `SHA1withRSA` / `SHA1` digest (hardcoded in `signWithJarSigner`):

```typescript
// cli/src/android/build.ts, lines 121–125
const signingArgs = [
'-sigalg', 'SHA1withRSA',
'-digestalg', 'SHA1',
...
```

SHA-1 signatures are rejected by Android for APKs targeting API 30+. So when building an **APK** without specifying a signing type, the default `jarsigner` path produces a binary that modern Android versions will refuse to install. `apksigner` should be the default for APK builds.

### Root cause

The signing dispatch in `cli/src/android/build.ts` is a simple binary branch with no awareness of `releaseType`:

```typescript
// cli/src/android/build.ts, lines 58–62
if (buildOptions.signingtype == 'jarsigner') {
await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
} else {
await signWithApkSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
}
```

The `else` branch routes **any value that is not `'jarsigner'`** — including `'apksigner'` — to `signWithApkSigner`, regardless of whether the output file is an APK or an AAB. When `releaseType` is `'AAB'` (the default), this calls:

```
apksigner sign --in app-release.aab --out app-release-signed.aab ...
```

`apksigner` expects an APK, which is a ZIP with `AndroidManifest.xml` at the root. An AAB stores its manifest at `base/manifest/AndroidManifest.xml`, so `apksigner` cannot find it and throws `ApkFormatException: Missing AndroidManifest.xml`, which surfaces as the misleading `MinSdkVersionException`.

The `releaseType` and `signingType` options are documented and treated as independent, but they are not — `apksigner` is fundamentally incompatible with AAB.

### Expected Behavior

The CLI should either:
- Throw a clear, actionable error before invoking `apksigner`, explaining that `apksigner` does not support AAB files and instructing the user to use `jarsigner` for AAB builds or switch `releaseType` to `'APK'`.
- Or automatically select the correct signing tool based on `releaseType`, with a warning if the user's explicit `signingType` is incompatible.

### Proposed fix

Replace the binary signing dispatch with logic that is aware of `releaseType`:

```typescript
if (releaseTypeIsAAB) {
if (buildOptions.signingtype === 'apksigner') {
throw `apksigner does not support AAB files. Set signingType to 'jarsigner' for AAB builds, or set androidReleaseType to 'APK'.`;
}
await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
} else {
// APK: prefer apksigner; only use jarsigner if explicitly requested
if (buildOptions.signingtype === 'jarsigner') {
await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
} else {
await signWithApkSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
}
}
```

This also requires:
- Removing the hardcoded `|| 'jarsigner'` default from `cli/src/tasks/build.ts` line 49, so `buildAndroid` can choose the correct tool based on `releaseType` when the user has not explicitly set `signingType`.
- Updating the `@default "jarsigner"` JSDoc comment in `cli/src/declarations.ts` line 278 to reflect that the default is now context-dependent (`'jarsigner'` for AAB, `'apksigner'` for APK).

### Additional Information

This issue is related to #6909

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.