appium-adb
Version:
Android Debug Bridge interface
137 lines • 6.46 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractUniversalApk = extractUniversalApk;
const logger_js_1 = require("../logger.js");
const path_1 = __importDefault(require("path"));
const support_1 = require("@appium/support");
const lru_cache_1 = require("lru-cache");
const helpers_js_1 = require("../helpers.js");
const async_lock_1 = __importDefault(require("async-lock"));
const bluebird_1 = __importDefault(require("bluebird"));
const crypto_1 = __importDefault(require("crypto"));
/** @type {LRUCache<string, string>} */
const AAB_CACHE = new lru_cache_1.LRUCache({
max: 10,
dispose: (extractedFilesRoot) => support_1.fs.rimraf(/** @type {string} */ (extractedFilesRoot)),
});
const AAB_CACHE_GUARD = new async_lock_1.default();
const UNIVERSAL_APK = 'universal.apk';
process.on('exit', () => {
if (!AAB_CACHE.size) {
return;
}
const paths = /** @type {string[]} */ ([...AAB_CACHE.values()]);
logger_js_1.log.debug(`Performing cleanup of ${paths.length} cached .aab ` +
support_1.util.pluralize('package', paths.length));
for (const appPath of paths) {
try {
// Asynchronous calls are not supported in onExit handler
support_1.fs.rimrafSync(appPath);
}
catch (e) {
logger_js_1.log.warn(( /** @type {Error} */(e)).message);
}
}
});
/**
* Builds a universal .apk from the given .aab package. See
* https://developer.android.com/studio/command-line/bundletool#generate_apks
* for more details.
*
* @this {import('../adb.js').ADB}
* @param {string} aabPath Full path to the source .aab package
* @param {import('./types').ApkCreationOptions} [opts={}]
* @returns The path to the resulting universal .apk. The .apk is stored in the internal cache
* by default.
* @throws {Error} If there was an error while creating the universal .apk
*/
async function extractUniversalApk(aabPath, opts = {}) {
if (!await support_1.fs.exists(aabPath)) {
throw new Error(`The file at '${aabPath}' either does not exist or is not accessible`);
}
const aabName = path_1.default.basename(aabPath);
const apkName = aabName.substring(0, aabName.length - path_1.default.extname(aabName).length) + '.apk';
const tmpRoot = await support_1.tempDir.openDir();
const tmpApksPath = path_1.default.join(tmpRoot, `${aabName}.apks`);
try {
return await AAB_CACHE_GUARD.acquire(aabPath, async () => {
const aabHash = await support_1.fs.hash(aabPath);
const { keystore, keystorePassword, keyAlias, keyPassword, } = opts;
let cacheHash = aabHash;
if (keystore) {
if (!await support_1.fs.exists(keystore)) {
throw new Error(`The keystore file at '${keystore}' either does not exist ` +
`or is not accessible`);
}
if (!keystorePassword || !keyAlias || !keyPassword) {
throw new Error('It is mandatory to also provide keystore password, key alias, ' +
'and key password if the keystore path is set');
}
const keystoreHash = await support_1.fs.hash(keystore);
const keyAliasHash = crypto_1.default.createHash('sha1');
keyAliasHash.update(keyAlias);
cacheHash = [cacheHash, keystoreHash, keyAliasHash.digest('hex')].join(':');
}
logger_js_1.log.debug(`Calculated the cache key for '${aabPath}': ${cacheHash}`);
if (AAB_CACHE.has(cacheHash)) {
const resultPath = path_1.default.resolve(/** @type {string} */ (AAB_CACHE.get(cacheHash)), apkName);
if (await support_1.fs.exists(resultPath)) {
return resultPath;
}
AAB_CACHE.delete(cacheHash);
}
await this.initAapt2();
const args = [
'build-apks',
'--aapt2', ( /** @type {import('./types').StringRecord} */(this.binaries)).aapt2,
'--bundle', aabPath,
'--output', tmpApksPath,
...(keystore ? [
'--ks', keystore,
'--ks-pass', `pass:${keystorePassword}`,
'--ks-key-alias', keyAlias,
'--key-pass', `pass:${keyPassword}`,
] : []),
'--mode=universal'
];
logger_js_1.log.debug(`Preparing universal .apks bundle from '${aabPath}'`);
await this.execBundletool(args, `Cannot build a universal .apks bundle from '${aabPath}'`);
logger_js_1.log.debug(`Unpacking universal application bundle at '${tmpApksPath}' to '${tmpRoot}'`);
await (0, helpers_js_1.unzipFile)(tmpApksPath, tmpRoot);
let universalApkPath;
const fileDeletionPromises = [];
const allFileNames = await support_1.fs.readdir(tmpRoot);
for (const fileName of allFileNames) {
const fullPath = path_1.default.join(tmpRoot, fileName);
if (fileName === UNIVERSAL_APK) {
universalApkPath = fullPath;
}
else {
fileDeletionPromises.push(support_1.fs.rimraf(fullPath));
}
}
try {
await bluebird_1.default.all(fileDeletionPromises);
}
catch { }
if (!universalApkPath) {
logger_js_1.log.debug(`The following items were extracted from the .aab bundle: ${allFileNames}`);
throw new Error(`${UNIVERSAL_APK} cannot be found in '${aabPath}' bundle. ` +
`Does the archive contain a valid application bundle?`);
}
const resultPath = path_1.default.join(tmpRoot, apkName);
logger_js_1.log.debug(`Found ${UNIVERSAL_APK} at '${universalApkPath}'. Caching it to '${resultPath}'`);
await support_1.fs.mv(universalApkPath, resultPath);
AAB_CACHE.set(cacheHash, tmpRoot);
return resultPath;
});
}
catch (e) {
await support_1.fs.rimraf(tmpRoot);
throw e;
}
}
//# sourceMappingURL=aab-utils.js.map