Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cli/src/android/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { runCommand } from '../util/subprocess';
export async function buildAndroid(config: Config, buildOptions: BuildCommandOptions): Promise<void> {
const releaseType = buildOptions.androidreleasetype ?? 'AAB';
const releaseTypeIsAAB = releaseType === 'AAB';
const signingType = buildOptions.signingtype ?? 'jarsigner';

if (releaseTypeIsAAB && signingType === 'apksigner') {
throw 'apksigner cannot sign Android App Bundles. Use jarsigner by setting android.buildOptions.signingType to "jarsigner" or passing "--signing-type jarsigner". Alternatively, build an APK by setting android.buildOptions.releaseType to "APK" or passing "--androidreleasetype APK".';
}

const flavor = buildOptions.flavor ?? '';
const arg = releaseTypeIsAAB ? `:app:bundle${flavor}Release` : `assemble${flavor}Release`;
const gradleArgs = [arg];
Expand Down Expand Up @@ -55,7 +61,7 @@ export async function buildAndroid(config: Config, buildOptions: BuildCommandOpt
`-release-signed.${releaseType.toLowerCase()}`,
);

if (buildOptions.signingtype == 'jarsigner') {
if (signingType === 'jarsigner') {
await signWithJarSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
} else {
await signWithApkSigner(config, buildOptions, releasePath, signedReleaseName, unsignedReleaseName);
Expand Down
100 changes: 100 additions & 0 deletions cli/test/build.android.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { buildAndroid } from '../src/android/build';
import type { AndroidConfig, Config } from '../src/definitions';
import type { BuildCommandOptions } from '../src/tasks/build';
import { runCommand } from '../src/util/subprocess';

jest.mock('../src/util/subprocess', () => ({
runCommand: jest.fn(),
}));

const mockedRunCommand = runCommand as jest.MockedFunction<typeof runCommand>;

function makeConfig(android: Partial<AndroidConfig>): Config {
return { android } as unknown as Config;
}

const config = makeConfig({
platformDirAbs: '/tmp/app/android',
appDirAbs: '/tmp/app/android/app',
});

const signingOptions: BuildCommandOptions = {
keystorepath: '/tmp/release.keystore',
keystorepass: 'store-password',
keystorealias: 'release',
keystorealiaspass: 'key-password',
configuration: 'Release',
};

describe('buildAndroid', () => {
beforeEach(() => {
mockedRunCommand.mockReset();
});

it('rejects apksigner for AAB before running external commands', async () => {
await expect(
buildAndroid(config, {
...signingOptions,
signingtype: 'apksigner',
}),
).rejects.toBe(
'apksigner cannot sign Android App Bundles. Use jarsigner by setting android.buildOptions.signingType to "jarsigner" or passing "--signing-type jarsigner". Alternatively, build an APK by setting android.buildOptions.releaseType to "APK" or passing "--androidreleasetype APK".',
);
expect(mockedRunCommand).not.toHaveBeenCalled();
});

it.each<{
name: string;
options: Partial<BuildCommandOptions>;
gradleTask: string;
signingTool: 'apksigner' | 'jarsigner';
unsignedArtifact: string;
signedArtifact: string;
}>([
{
name: 'uses jarsigner for the default AAB build',
options: {},
gradleTask: ':app:bundleRelease',
signingTool: 'jarsigner',
unsignedArtifact: '/tmp/app/android/app/build/outputs/bundle/release/app-release.aab',
signedArtifact: '/tmp/app/android/app/build/outputs/bundle/release/app-release-signed.aab',
},
{
name: 'uses jarsigner for an explicit AAB build',
options: { androidreleasetype: 'AAB', signingtype: 'jarsigner' },
gradleTask: ':app:bundleRelease',
signingTool: 'jarsigner',
unsignedArtifact: '/tmp/app/android/app/build/outputs/bundle/release/app-release.aab',
signedArtifact: '/tmp/app/android/app/build/outputs/bundle/release/app-release-signed.aab',
},
{
name: 'uses apksigner for an APK build when requested',
options: { androidreleasetype: 'APK', signingtype: 'apksigner' },
gradleTask: 'assembleRelease',
signingTool: 'apksigner',
unsignedArtifact: '/tmp/app/android/app/build/outputs/apk/release/app-release-unsigned.apk',
signedArtifact: '/tmp/app/android/app/build/outputs/apk/release/app-release-signed.apk',
},
{
name: 'uses jarsigner for an APK build when requested',
options: { androidreleasetype: 'APK', signingtype: 'jarsigner' },
gradleTask: 'assembleRelease',
signingTool: 'jarsigner',
unsignedArtifact: '/tmp/app/android/app/build/outputs/apk/release/app-release-unsigned.apk',
signedArtifact: '/tmp/app/android/app/build/outputs/apk/release/app-release-signed.apk',
},
])('$name', async ({ options, gradleTask, signingTool, unsignedArtifact, signedArtifact }) => {
await expect(buildAndroid(config, { ...signingOptions, ...options })).resolves.toBeUndefined();

expect(mockedRunCommand).toHaveBeenCalledTimes(2);
expect(mockedRunCommand).toHaveBeenNthCalledWith(1, './gradlew', [gradleTask], {
cwd: '/tmp/app/android',
});
expect(mockedRunCommand).toHaveBeenNthCalledWith(
2,
signingTool,
expect.arrayContaining([unsignedArtifact, signedArtifact]),
{ cwd: '/tmp/app/android' },
);
});
});