Skip to content

Commit

Permalink
Introduce feature to zipalign apks in addition to signing
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin David authored Mar 15, 2022
1 parent 51345d3 commit 73cd0ad
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 62 deletions.
17 changes: 14 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ on:
push:
branches:
- master
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest
name: Test action

steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
name: Checkout code

- name: Run tests
Expand All @@ -23,6 +24,16 @@ jobs:
with:
path: __tests__/test_key_b64.txt

- uses: ./
name: Zipalign and Sign APK
with:
releaseDirectory: __tests__/apk-zip
signingKeyBase64: ${{ steps.get_key.outputs.text }}
alias: test_key
keyStorePassword: android
keyPassword: android
zipAlign: true

- uses: ./
name: Sign APK
with:
Expand Down Expand Up @@ -50,8 +61,8 @@ jobs:
- uses: actions/checkout@v3
name: Checkout code

- name: Update v1 Tag
- name: Update v1.1 Tag
uses: hole19/git-tag-action@master
env:
TAG: v1
TAG: v1.1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Sign Android Release Action
# `zipalign` & Sign Android Release Action

This action will help you sign an Android `.apk` or `.aab` (Android App Bundle) file for release.
This action will help you `zipalign` and sign an Android `.apk` or `.aab` (Android App Bundle) file for release. Based on https://github.com/r0adkll/sign-android-release.

## Inputs

Expand Down Expand Up @@ -31,9 +31,13 @@ Then copy the contents of the `.txt` file to your GH secrets

**Optional:** The private key password for your signing keystore

### `zipAlign`

**Optional:** True to run `zipalign` on the `.apk` to perform the operation, rather than just verify zipalign.

## ENV: `BUILD_TOOLS_VERSION`

**Optional:** You can manually specify a version of build-tools to use. We use `29.0.3` by default.
**Optional:** You can manually specify a version of build-tools to use. We use `32.0.0` by default.

## Outputs

Expand Down Expand Up @@ -68,7 +72,7 @@ steps:
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
# override default build-tools version (29.0.3) -- optional
# override default build-tools version (32.0.0) -- optional
BUILD_TOOLS_VERSION: "30.0.2"

# Example use of `signedReleaseFile` output -- not needed
Expand Down Expand Up @@ -116,14 +120,15 @@ before being used in a release action.

```yaml
steps:
- uses: r0adkll/sign-android-release@v1
- uses: kevin-david/zipalign-sign-android-release@v1.1
id: sign_app
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
zipAlign: true
- uses: jungwinter/split@v1
id: signed_files
Expand Down
Binary file added __tests__/apk-zip/app-release-unsigned.apk
Binary file not shown.
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ inputs:
keyPassword:
description: 'The password for the key'
required: false
zipAlign:
description: 'Actually zipalign the apk, rather than just verifying it'
required: false
default: 'false'
outputs:
signedReleaseFile:
description: 'The signed release APK or AAB file, if single'
Expand Down
46 changes: 33 additions & 13 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand All @@ -37,42 +41,58 @@ const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const io = __importStar(require("./io-utils"));
function run() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
try {
if (process.env.DEBUG_ACTION === 'true') {
core.debug("DEBUG FLAG DETECTED, SHORTCUTTING ACTION.");
return;
}
const releaseDir = core.getInput('releaseDirectory');
const signingKeyBase64 = core.getInput('signingKeyBase64');
const alias = core.getInput('alias');
const keyStorePassword = core.getInput('keyStorePassword');
const releaseDir = core.getInput('releaseDirectory', { required: true });
const signingKeyBase64 = core.getInput('signingKeyBase64', { required: true });
const alias = core.getInput('alias', { required: true });
const keyStorePassword = core.getInput('keyStorePassword', { required: true });
const keyPassword = core.getInput('keyPassword');
const zipAlign = core.getBooleanInput('zipAlign');
console.log(`Preparing to sign key @ ${releaseDir} with signing key`);
// 1. Find release files
const releaseFiles = io.findReleaseFiles(releaseDir);
if (releaseFiles !== undefined) {
if (releaseFiles !== undefined && releaseFiles.length !== 0) {
// 3. Now that we have a release files, decode and save the signing key
const signingKey = path_1.default.join(releaseDir, 'signingKey.jks');
fs_1.default.writeFileSync(signingKey, signingKeyBase64, 'base64');
// 4. Now zipalign and sign each one of the the release files
let signedReleaseFiles = [];
let index = 0;
for (let releaseFile of releaseFiles) {
core.debug(`Found release to sign: ${releaseFile.name}`);
const releaseFilePath = path_1.default.join(releaseDir, releaseFile.name);
let signedReleaseFile = '';
if (releaseFile.name.endsWith('.apk')) {
signedReleaseFile = yield signing_1.signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword);
signedReleaseFile = yield (0, signing_1.signApkFile)(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword, zipAlign);
}
else if (releaseFile.name.endsWith('.aab')) {
signedReleaseFile = yield signing_1.signAabFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword);
signedReleaseFile = yield (0, signing_1.signAabFile)(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword);
}
else {
core.error('No valid release file to sign, abort.');
core.setFailed('No valid release file to sign.');
}
core.debug('Release signed! Setting outputs.');
core.exportVariable("SIGNED_RELEASE_FILE", signedReleaseFile);
core.setOutput('signedReleaseFile', signedReleaseFile);
// Each signed release file is stored in a separate variable + output.
core.exportVariable(`SIGNED_RELEASE_FILE_${index}`, signedReleaseFile);
core.setOutput(`signedReleaseFile${index}`, signedReleaseFile);
signedReleaseFiles.push(signedReleaseFile);
++index;
}
// All signed release files are stored in a merged variable + output.
core.exportVariable(`SIGNED_RELEASE_FILES`, signedReleaseFiles.join(":"));
core.setOutput('signedReleaseFiles', signedReleaseFiles.join(":"));
core.exportVariable(`NOF_SIGNED_RELEASE_FILES`, `${signedReleaseFiles.length}`);
core.setOutput(`nofSignedReleaseFiles`, `${signedReleaseFiles.length}`);
// When there is one and only one signed release file, stoire it in a specific variable + output.
if (signedReleaseFiles.length == 1) {
core.exportVariable(`SIGNED_RELEASE_FILE`, signedReleaseFiles[0]);
core.setOutput('signedReleaseFile', signedReleaseFiles[0]);
}
console.log('Releases signed!');
}
Expand All @@ -82,7 +102,7 @@ function run() {
}
}
catch (error) {
core.setFailed(error.message);
core.setFailed((_a = (error === null || error === void 0 ? void 0 : error.message)) !== null && _a !== void 0 ? _a : error);
}
});
}
Expand Down
48 changes: 33 additions & 15 deletions lib/signing.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
Expand All @@ -14,7 +18,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Expand All @@ -34,11 +38,12 @@ const core = __importStar(require("@actions/core"));
const io = __importStar(require("@actions/io"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
function signApkFile(apkFile, signingKeyFile, alias, keyStorePassword, keyPassword) {
function signApkFile(apkFile, signingKeyFile, alias, keyStorePassword, keyPassword, doZipAlign) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
core.debug("Zipaligning APK file");
// Find zipalign executable
const buildToolsVersion = process.env.BUILD_TOOLS_VERSION || '29.0.3';
const buildToolsVersion = process.env.BUILD_TOOLS_VERSION || '32.0.0';
const androidHome = process.env.ANDROID_HOME;
const buildTools = path.join(androidHome, `build-tools/${buildToolsVersion}`);
if (!fs.existsSync(buildTools)) {
Expand All @@ -47,16 +52,29 @@ function signApkFile(apkFile, signingKeyFile, alias, keyStorePassword, keyPasswo
const zipAlign = path.join(buildTools, 'zipalign');
core.debug(`Found 'zipalign' @ ${zipAlign}`);
// Align the apk file
const alignedApkFile = apkFile.replace('.apk', '-aligned.apk');
yield exec.exec(`"${zipAlign}"`, [
'-c',
'-v', '4',
apkFile
]);
yield exec.exec(`"cp"`, [
apkFile,
alignedApkFile
]);
if (doZipAlign === true) {
const unAlignedApk = apkFile + ".unaligned";
fs.renameSync(apkFile, unAlignedApk);
yield exec.exec(`"${zipAlign}"`, [
'-v',
'4',
unAlignedApk,
apkFile
]);
}
// Verify alignment
try {
yield exec.exec(`"${zipAlign}"`, [
'-c',
'-v',
'4',
apkFile
]);
}
catch (e) {
console.error(`zipalign verification failed: ${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : e}.\n Did you mean to run this with \`zipAlign: true\`?`);
throw e;
}
core.debug("Signing APK file");
// find apksigner path
const apkSigner = path.join(buildTools, 'apksigner');
Expand All @@ -73,7 +91,7 @@ function signApkFile(apkFile, signingKeyFile, alias, keyStorePassword, keyPasswo
if (keyPassword) {
args.push('--key-pass', `pass:${keyPassword}`);
}
args.push(alignedApkFile);
args.push(apkFile);
yield exec.exec(`"${apkSigner}"`, args);
// Verify
core.debug("Verifying Signed APK");
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "sign-android-release",
"version": "1.0.4",
"name": "zipalign-sign-android-release",
"version": "1.1.0",
"private": true,
"description": "GitHub action used to sign Android release packages",
"description": "GitHub action used to zipalign and sign Android release packages",
"main": "lib/main.js",
"scripts": {
"build": "tsc",
Expand All @@ -17,7 +17,7 @@
"node",
"setup"
],
"author": "r0adkll",
"author": "kevin-david",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.6.0",
Expand Down
17 changes: 9 additions & 8 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as core from '@actions/core';
import {signAabFile, signApkFile} from "./signing";
import { signAabFile, signApkFile } from "./signing";
import path from "path";
import fs from "fs";
import * as io from "./io-utils";
Expand All @@ -11,11 +11,12 @@ async function run() {
return;
}

const releaseDir = core.getInput('releaseDirectory');
const signingKeyBase64 = core.getInput('signingKeyBase64');
const alias = core.getInput('alias');
const keyStorePassword = core.getInput('keyStorePassword');
const releaseDir = core.getInput('releaseDirectory', { required: true });
const signingKeyBase64 = core.getInput('signingKeyBase64', { required: true });
const alias = core.getInput('alias', { required: true });
const keyStorePassword = core.getInput('keyStorePassword', { required: true });
const keyPassword = core.getInput('keyPassword');
const zipAlign = core.getBooleanInput('zipAlign')

console.log(`Preparing to sign key @ ${releaseDir} with signing key`);

Expand All @@ -27,14 +28,14 @@ async function run() {
fs.writeFileSync(signingKey, signingKeyBase64, 'base64');

// 4. Now zipalign and sign each one of the the release files
let signedReleaseFiles:string[] = [];
let signedReleaseFiles: string[] = [];
let index = 0;
for (let releaseFile of releaseFiles) {
core.debug(`Found release to sign: ${releaseFile.name}`);
const releaseFilePath = path.join(releaseDir, releaseFile.name);
let signedReleaseFile = '';
if (releaseFile.name.endsWith('.apk')) {
signedReleaseFile = await signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword);
signedReleaseFile = await signApkFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword, zipAlign);
} else if (releaseFile.name.endsWith('.aab')) {
signedReleaseFile = await signAabFile(releaseFilePath, signingKey, alias, keyStorePassword, keyPassword);
} else {
Expand Down Expand Up @@ -66,7 +67,7 @@ async function run() {
core.setFailed('No release files (.apk or .aab) could be found.');
}
} catch (error) {
core.setFailed(error.message);
core.setFailed(((error as any)?.message) ?? error);
}
}

Expand Down
Loading

0 comments on commit 73cd0ad

Please sign in to comment.