UNPKG

appium-adb

Version:

Android Debug Bridge interface

128 lines (122 loc) 4.49 kB
import {util} from '@appium/support'; import {exec, type ExecError} from 'teen_process'; import {log} from '../../logger'; import type {ADB} from '../../adb'; import type {ApkManifest} from '../../tools/types'; /** * Reads and parses Android manifest metadata from an APK via `aapt2`. * * @param apkPath - Local path to the APK file * @returns Parsed manifest data */ export async function readPackageManifest(this: ADB, apkPath: string): Promise<ApkManifest> { await this.initAapt2(); const aapt2Binary = this.binaries?.aapt2; if (!aapt2Binary) { throw new Error('aapt2 binary is not available'); } const args = ['dump', 'badging', apkPath]; log.debug(`Reading package manifest: '${util.quote([aapt2Binary, ...args])}'`); let stdout: string; try { ({stdout} = await exec(aapt2Binary, args)); } catch (e: unknown) { const error = e as ExecError; const prefix = `Cannot read the manifest from '${apkPath}'`; const suffix = `Original error: ${error.stderr || error.message}`; if (error.stderr && error.stderr.includes(`Unable to open 'badging'`)) { throw new Error(`${prefix}. Update build tools to use a newer aapt2 version. ${suffix}`, { cause: e, }); } throw new Error(`${prefix}. ${suffix}`, {cause: e}); } const extractValue = ( line: string, propPattern: RegExp, valueTransformer: ((x: string) => any) | null, ): any => { const match = propPattern.exec(line); if (match) { return valueTransformer ? valueTransformer(match[1]) : match[1]; } return undefined; }; const extractArray = ( line: string, propPattern: RegExp, valueTransformer: ((x: string) => any) | null, ): any[] => { let match: RegExpExecArray | null; const resultArray: any[] = []; while ((match = propPattern.exec(line))) { resultArray.push(valueTransformer ? valueTransformer(match[1]) : match[1]); } return resultArray; }; const toInt = (x: string): number => parseInt(x, 10); const result: ApkManifest = { name: '', versionCode: 0, minSdkVersion: 0, compileSdkVersion: 0, usesPermissions: [], launchableActivity: { name: '', }, architectures: [], locales: [], densities: [], }; for (const line of stdout.split('\n')) { if (line.startsWith('package:')) { for (const [name, pattern, transformer] of [ ['name', /name='([^']+)'/, null], ['versionCode', /versionCode='([^']+)'/, toInt], ['versionName', /versionName='([^']+)'/, null], ['platformBuildVersionName', /platformBuildVersionName='([^']+)'/, null], ['platformBuildVersionCode', /platformBuildVersionCode='([^']+)'/, toInt], ['compileSdkVersion', /compileSdkVersion='([^']+)'/, toInt], ['compileSdkVersionCodename', /compileSdkVersionCodename='([^']+)'/, null], ] as const) { const value = extractValue(line, pattern, transformer); if (value !== undefined) { (result as Record<string, any>)[name] = value; } } } else if (line.startsWith('sdkVersion:') || line.startsWith('minSdkVersion:')) { const value = extractValue(line, /[sS]dkVersion:'([^']+)'/, toInt); if (value) { result.minSdkVersion = value; } } else if (line.startsWith('targetSdkVersion:')) { const value = extractValue(line, /targetSdkVersion:'([^']+)'/, toInt); if (value) { result.targetSdkVersion = value; } } else if (line.startsWith('uses-permission:')) { const value = extractValue(line, /name='([^']+)'/, null); if (value) { result.usesPermissions.push(value); } } else if (line.startsWith('launchable-activity:')) { for (const [name, pattern] of [ ['name', /name='([^']+)'/], ['label', /label='([^']+)'/], ['icon', /icon='([^']+)'/], ] as const) { const value = extractValue(line, pattern, null); if (value) { (result.launchableActivity as Record<string, any>)[name] = value; } } } else if (line.startsWith('locales:')) { result.locales = extractArray(line, /'([^']+)'/g, null) as string[]; } else if (line.startsWith('native-code:')) { result.architectures = extractArray(line, /'([^']+)'/g, null) as string[]; } else if (line.startsWith('densities:')) { result.densities = extractArray(line, /'([^']+)'/g, toInt) as number[]; } } return result; }