UNPKG

@bubblewrap/core

Version:

Core Library to generate, build and sign TWA projects

232 lines (231 loc) 9.7 kB
"use strict"; /* * Copyright 2019 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AndroidSdkTools = exports.BUILD_TOOLS_VERSION = void 0; const fs = require("fs"); const path = require("path"); const util = require("../util"); const Log_1 = require("../../lib/Log"); const Result_1 = require("../../lib/Result"); const ValidatePathError_1 = require("../errors/ValidatePathError"); exports.BUILD_TOOLS_VERSION = '34.0.0'; /** * Wraps functionality of the Android SDK Tools and allows them to be invoked programatically. */ class AndroidSdkTools { static async create(process, config, jdkHelper, log = new Log_1.ConsoleLog('AndroidSdkTools')) { // unwrap will throw an error in case that the the path is valid and else will do nothing. (await AndroidSdkTools.validatePath(config.androidSdkPath)).unwrap(); try { return new AndroidSdkTools(process, config, jdkHelper, log); } catch (error) { if (typeof error === 'string') { throw new Error(error); } else { throw error; } } } /** * Constructs a new instance of AndroidSdkTools. * * @param {NodeJS.Process} process information from the OS process * @param {Config} config the bubblewrap general configuration * @param {jdkHelper} jdkHelper the JDK information to be used by the Android SDK */ constructor(process, config, jdkHelper, log = new Log_1.ConsoleLog('AndroidSdkTools')) { this.log = log; this.process = process; this.config = config; this.jdkHelper = jdkHelper; if (this.process.platform === 'win32') { this.pathJoin = path.win32.join; } else { this.pathJoin = path.posix.join; } } /** * Installs the build tools into the the Android SDK. Equivalent to running * * `tools/bin/sdkmanager --install "build-tools;29.0.2"` */ async installBuildTools() { const env = this.getEnv(); // The escape char allows us to pass a directory with spaces as sdk_root. On Windows, // those are not properly handled by the Android SDK, so we try running without wrapping the // value. // TODO(andreban): Check for spaces in the path and throw an Error if one is found. let sdkRootEscapeChar = '"'; let sdkManagerPath = this.pathJoin(this.getAndroidHome(), '/tools/bin/sdkmanager'); if (this.process.platform === 'win32') { sdkRootEscapeChar = ''; sdkManagerPath += '.bat'; } if (!fs.existsSync(sdkManagerPath)) { // Android SDK version `6858069` and above doesn't have a `tools` folder anymore. sdkManagerPath = this.pathJoin(this.getAndroidHome(), '/bin/sdkmanager'); if (this.process.platform === 'win32') { sdkManagerPath += '.bat'; } if (!fs.existsSync(sdkManagerPath)) { throw new Error(`Could not find sdkmanager at: ${sdkManagerPath}`); } } this.log.info('Installing Build Tools'); await util.execInteractive(sdkManagerPath, ['--install', `"build-tools;${exports.BUILD_TOOLS_VERSION}"`, // setting ANDROID_HOME via this.getEnv() should be enough, but version 6200805 of the // the Android Command Line tools don't work properly if sdk_root is not set. `--sdk_root=${sdkRootEscapeChar}${this.getAndroidHome()}${sdkRootEscapeChar}`], env); } /** * Verifies if the build-tools are installed on the Android SDK. */ async checkBuildTools() { const buildToolsPath = this.pathJoin(this.getAndroidHome(), '/build-tools/', exports.BUILD_TOOLS_VERSION); return fs.existsSync(buildToolsPath); } /** * Returns the path to the Android SDK. * @returns {string} the path to the Android SDK. */ getAndroidHome() { return this.pathJoin(this.config.androidSdkPath, '/'); } /** * Creates a Node Process with the correct ANDROID_HOME information * @returns {NodeJS.ProcessEnv} the env with ANDROID_HOME set */ getEnv() { const env = this.jdkHelper.getEnv(); env['ANDROID_HOME'] = this.getAndroidHome(); return env; } /** * Invokes the zipalign tool from the Android SDK with the following flags: * -f : overwrite existing outfile.zip. * -v : verbose output. * -p 4 : align all libraries to the 32-bit page boundary. * More information on zipalign can be found here: * https://developer.android.com/studio/command-line/zipalign * @param {string} input path to the input file. * @param {string} output path to the output file. */ async zipalign(input, output) { const env = this.getEnv(); const zipalignCmd = [ `"${this.pathJoin(this.getAndroidHome(), `/build-tools/${exports.BUILD_TOOLS_VERSION}/zipalign`)}"`, '-v -f -p 4', input, output, ]; await util.execute(zipalignCmd, env); } /** * Invokes the zipalign tool from the Android SDK with the following flags: * -c : confirm the alignment of the given file. * -v : verbose output. * -p 4 : align all libraries to the 32-bit page boundary. * More information on zipalign can be found here: * https://developer.android.com/studio/command-line/zipalign * @param {string} input path to the input file. */ async zipalignOnlyVerification(input) { const env = this.getEnv(); const zipalignCmd = [ `"${this.pathJoin(this.getAndroidHome(), `/build-tools/${exports.BUILD_TOOLS_VERSION}/zipalign`)}"`, '-v -c -p 4', input, ]; await util.execute(zipalignCmd, env); } /** * Signs an Android APK, with they keystore * @param {string} keystore path to the keystore * @param {string} ksPass keystore password * @param {string} alias key alias * @param {string} keyPass key password * @param {string} input path to the input APK file * @param {string} output path where the signed APK will be generated */ async apksigner(keystore, ksPass, alias, keyPass, input, output) { const env = this.getEnv(); const apkSignerParams = [ 'sign', '--ks', keystore, '--ks-key-alias', alias, '--ks-pass', `pass:${ksPass}`, '--key-pass', `pass:${keyPass}`, '--out', output, input, ]; // This is a workaround for https://issuetracker.google.com/issues/150888434, where // find_java.bat is unable to find the java command on Windows. // We run apksigner.jar directly instead of invoking the bat. if (this.process.platform === 'win32') { const javaCmd = [ '-Xmx1024M', '-Xss1m', '-jar', this.pathJoin(this.getAndroidHome(), `/build-tools/${exports.BUILD_TOOLS_VERSION}/lib/apksigner.jar`), ]; javaCmd.push(...apkSignerParams); await this.jdkHelper.runJava(javaCmd); return; } const apksignerCmd = this.pathJoin(this.getAndroidHome(), `/build-tools/${exports.BUILD_TOOLS_VERSION}/apksigner`); await util.executeFile(apksignerCmd, apkSignerParams, env); } /** * Installs an APK on an a device connected to the computer. * @param apkFilePath the path to the APK to be installed */ async install(apkFilePath, passthroughArgs = []) { if (!fs.existsSync(apkFilePath)) { throw new Error(`Could not find APK file at ${apkFilePath}`); } const env = this.getEnv(); const installCmd = [ `"${this.pathJoin(this.getAndroidHome(), '/platform-tools/adb')}"`, 'install', '-r', // Replace app if another with the same package id already installed. ...passthroughArgs, apkFilePath, ]; await util.execute(installCmd, env, this.log); } /** * Checks if `sdkPath` is valid. * @param {string} sdkPath the path to the sdk. */ static async validatePath(sdkPath) { const toolsPath = path.join(sdkPath, 'tools'); const binPath = path.join(sdkPath, 'bin'); // Checks if the path provided is valid. Older versions of the the Android SDK add the // initial files inside the `tools` folder. Version `6858069` and above add it directly // to the `bin` folder. if (!fs.existsSync(sdkPath) || (!fs.existsSync(toolsPath)) && !fs.existsSync(binPath)) { return Result_1.Result.error(new ValidatePathError_1.ValidatePathError('The provided androidSdk isn\'t correct.', 'PathIsNotCorrect')); } ; return Result_1.Result.ok(sdkPath); } } exports.AndroidSdkTools = AndroidSdkTools;