forked from olcxjas-softworks/LarpixClient
Add capacitorjs runtime
This commit is contained in:
parent
d0ece489ee
commit
f90c0e6c40
8362 changed files with 1502407 additions and 1 deletions
318
node_modules/native-run/dist/android/utils/adb.js
generated
vendored
Normal file
318
node_modules/native-run/dist/android/utils/adb.js
generated
vendored
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.execAdb = exports.unforwardPorts = exports.forwardPorts = exports.parseAdbDevices = exports.startActivity = exports.parseAdbInstallOutput = exports.ADBEvent = exports.uninstallApp = exports.closeApp = exports.installApk = exports.waitForClose = exports.waitForBoot = exports.waitForDevice = exports.getDeviceProperties = exports.getDeviceProperty = exports.getDevices = void 0;
|
||||
const child_process_1 = require("child_process");
|
||||
const Debug = require("debug");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const split2 = require("split2");
|
||||
const through2 = require("through2");
|
||||
const errors_1 = require("../../errors");
|
||||
const process_1 = require("../../utils/process");
|
||||
const sdk_1 = require("./sdk");
|
||||
const modulePrefix = 'native-run:android:utils:adb';
|
||||
const ADB_GETPROP_MAP = new Map([
|
||||
['ro.product.manufacturer', 'manufacturer'],
|
||||
['ro.product.model', 'model'],
|
||||
['ro.product.name', 'product'],
|
||||
['ro.build.version.sdk', 'sdkVersion'],
|
||||
]);
|
||||
async function getDevices(sdk) {
|
||||
const debug = Debug(`${modulePrefix}:${getDevices.name}`);
|
||||
const args = ['devices', '-l'];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const stdout = await execAdb(sdk, args, { timeout: 5000 });
|
||||
const devices = parseAdbDevices(stdout);
|
||||
await Promise.all(devices.map(async (device) => {
|
||||
const properties = await getDeviceProperties(sdk, device);
|
||||
for (const [prop, deviceProp] of ADB_GETPROP_MAP.entries()) {
|
||||
const value = properties[prop];
|
||||
if (value) {
|
||||
device[deviceProp] = value;
|
||||
}
|
||||
}
|
||||
}));
|
||||
debug('Found adb devices: %O', devices);
|
||||
return devices;
|
||||
}
|
||||
exports.getDevices = getDevices;
|
||||
async function getDeviceProperty(sdk, device, property) {
|
||||
const debug = Debug(`${modulePrefix}:${getDeviceProperty.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'getprop', property];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const stdout = await execAdb(sdk, args, { timeout: 5000 });
|
||||
return stdout.trim();
|
||||
}
|
||||
exports.getDeviceProperty = getDeviceProperty;
|
||||
async function getDeviceProperties(sdk, device) {
|
||||
const debug = Debug(`${modulePrefix}:${getDeviceProperties.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'getprop'];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const stdout = await execAdb(sdk, args, { timeout: 5000 });
|
||||
const re = /^\[([a-z0-9.]+)\]: \[(.*)\]$/;
|
||||
const propAllowList = [...ADB_GETPROP_MAP.keys()];
|
||||
const properties = {};
|
||||
for (const line of stdout.split(os.EOL)) {
|
||||
const m = line.match(re);
|
||||
if (m) {
|
||||
const [, key, value] = m;
|
||||
if (propAllowList.includes(key)) {
|
||||
properties[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
exports.getDeviceProperties = getDeviceProperties;
|
||||
async function waitForDevice(sdk, serial) {
|
||||
const debug = Debug(`${modulePrefix}:${waitForDevice.name}`);
|
||||
const args = ['-s', serial, 'wait-for-any-device'];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
debug('Device %s is connected to ADB!', serial);
|
||||
}
|
||||
exports.waitForDevice = waitForDevice;
|
||||
async function waitForBoot(sdk, device) {
|
||||
const debug = Debug(`${modulePrefix}:${waitForBoot.name}`);
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(async () => {
|
||||
const booted = await getDeviceProperty(sdk, device, 'dev.bootcomplete');
|
||||
if (booted) {
|
||||
debug('Device %s is booted!', device.serial);
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
exports.waitForBoot = waitForBoot;
|
||||
async function waitForClose(sdk, device, app) {
|
||||
const debug = Debug(`${modulePrefix}:${waitForClose.name}`);
|
||||
const args = ['-s', device.serial, 'shell', `ps | grep ${app}`];
|
||||
return new Promise((resolve) => {
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
}
|
||||
catch (e) {
|
||||
debug('Error received from adb: %O', e);
|
||||
debug('App %s no longer found in process list for %s', app, device.serial);
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
exports.waitForClose = waitForClose;
|
||||
async function installApk(sdk, device, apk) {
|
||||
const debug = Debug(`${modulePrefix}:${installApk.name}`);
|
||||
const platformTools = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'platform-tools'));
|
||||
const adbBin = path.join(platformTools.location, 'adb');
|
||||
const args = ['-s', device.serial, 'install', '-r', '-t', apk];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
const p = (0, child_process_1.spawn)(adbBin, args, {
|
||||
stdio: 'pipe',
|
||||
env: (0, sdk_1.supplementProcessEnv)(sdk),
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
p.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
reject(new errors_1.ADBException(`Non-zero exit code from adb: ${code}`));
|
||||
}
|
||||
});
|
||||
p.on('error', (err) => {
|
||||
debug('adb install error: %O', err);
|
||||
reject(err);
|
||||
});
|
||||
p.stderr.pipe(split2()).pipe(through2((chunk, enc, cb) => {
|
||||
const line = chunk.toString();
|
||||
debug('adb install: %O', line);
|
||||
const event = parseAdbInstallOutput(line);
|
||||
if (event === ADBEvent.IncompatibleUpdateFailure) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_INCOMPATIBLE_UPDATE));
|
||||
}
|
||||
else if (event === ADBEvent.NewerVersionOnDeviceFailure) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_VERSION_DOWNGRADE));
|
||||
}
|
||||
else if (event === ADBEvent.NewerSdkRequiredOnDeviceFailure) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_MIN_SDK_VERSION));
|
||||
}
|
||||
else if (event === ADBEvent.NoCertificates) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_NO_CERTIFICATES));
|
||||
}
|
||||
else if (event === ADBEvent.NotEnoughSpace) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_NOT_ENOUGH_SPACE));
|
||||
}
|
||||
else if (event === ADBEvent.DeviceOffline) {
|
||||
reject(new errors_1.ADBException(`Encountered adb error: ${ADBEvent[event]}.`, errors_1.ERR_DEVICE_OFFLINE));
|
||||
}
|
||||
cb();
|
||||
}));
|
||||
});
|
||||
}
|
||||
exports.installApk = installApk;
|
||||
async function closeApp(sdk, device, app) {
|
||||
const debug = Debug(`${modulePrefix}:${closeApp.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'am', 'force-stop', app];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
}
|
||||
exports.closeApp = closeApp;
|
||||
async function uninstallApp(sdk, device, app) {
|
||||
const debug = Debug(`${modulePrefix}:${uninstallApp.name}`);
|
||||
const args = ['-s', device.serial, 'uninstall', app];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args);
|
||||
}
|
||||
exports.uninstallApp = uninstallApp;
|
||||
var ADBEvent;
|
||||
(function (ADBEvent) {
|
||||
ADBEvent[ADBEvent["IncompatibleUpdateFailure"] = 0] = "IncompatibleUpdateFailure";
|
||||
ADBEvent[ADBEvent["NewerVersionOnDeviceFailure"] = 1] = "NewerVersionOnDeviceFailure";
|
||||
ADBEvent[ADBEvent["NewerSdkRequiredOnDeviceFailure"] = 2] = "NewerSdkRequiredOnDeviceFailure";
|
||||
ADBEvent[ADBEvent["NoCertificates"] = 3] = "NoCertificates";
|
||||
ADBEvent[ADBEvent["NotEnoughSpace"] = 4] = "NotEnoughSpace";
|
||||
ADBEvent[ADBEvent["DeviceOffline"] = 5] = "DeviceOffline";
|
||||
})(ADBEvent = exports.ADBEvent || (exports.ADBEvent = {}));
|
||||
function parseAdbInstallOutput(line) {
|
||||
const debug = Debug(`${modulePrefix}:${parseAdbInstallOutput.name}`);
|
||||
let event;
|
||||
if (line.includes('INSTALL_FAILED_UPDATE_INCOMPATIBLE')) {
|
||||
event = ADBEvent.IncompatibleUpdateFailure;
|
||||
}
|
||||
else if (line.includes('INSTALL_FAILED_VERSION_DOWNGRADE')) {
|
||||
event = ADBEvent.NewerVersionOnDeviceFailure;
|
||||
}
|
||||
else if (line.includes('INSTALL_FAILED_OLDER_SDK')) {
|
||||
event = ADBEvent.NewerSdkRequiredOnDeviceFailure;
|
||||
}
|
||||
else if (line.includes('INSTALL_PARSE_FAILED_NO_CERTIFICATES')) {
|
||||
event = ADBEvent.NoCertificates;
|
||||
}
|
||||
else if (line.includes('INSTALL_FAILED_INSUFFICIENT_STORAGE') || line.includes('not enough space')) {
|
||||
event = ADBEvent.NotEnoughSpace;
|
||||
}
|
||||
else if (line.includes('device offline')) {
|
||||
event = ADBEvent.DeviceOffline;
|
||||
}
|
||||
if (typeof event !== 'undefined') {
|
||||
debug('Parsed event from adb install output: %s', ADBEvent[event]);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
exports.parseAdbInstallOutput = parseAdbInstallOutput;
|
||||
async function startActivity(sdk, device, packageName, activityName) {
|
||||
const debug = Debug(`${modulePrefix}:${startActivity.name}`);
|
||||
const args = ['-s', device.serial, 'shell', 'am', 'start', '-W', '-n', `${packageName}/${activityName}`];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args, { timeout: 5000 });
|
||||
}
|
||||
exports.startActivity = startActivity;
|
||||
function parseAdbDevices(output) {
|
||||
const debug = Debug(`${modulePrefix}:${parseAdbDevices.name}`);
|
||||
const re = /^([\S]+)\s+([a-z\s]+)\s+(.*)$/;
|
||||
const ipRe = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+$/;
|
||||
const lines = output.split(os.EOL);
|
||||
debug('Parsing adb devices from output lines: %O', lines);
|
||||
const devices = [];
|
||||
for (const line of lines) {
|
||||
if (line && !line.startsWith('List')) {
|
||||
const m = line.match(re);
|
||||
if (m) {
|
||||
const [, serial, state, description] = m;
|
||||
const properties = description
|
||||
.split(/\s+/)
|
||||
.map((prop) => (prop.includes(':') ? prop.split(':') : undefined))
|
||||
.filter((kv) => typeof kv !== 'undefined' && kv.length >= 2)
|
||||
.reduce((acc, [k, v]) => {
|
||||
if (k && v) {
|
||||
acc[k.trim()] = v.trim();
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
const isIP = !!serial.match(ipRe);
|
||||
const isGenericDevice = (properties['device'] || '').startsWith('generic');
|
||||
const type = 'usb' in properties || isIP || !serial.startsWith('emulator') || !isGenericDevice ? 'hardware' : 'emulator';
|
||||
const connection = 'usb' in properties ? 'usb' : isIP ? 'tcpip' : null;
|
||||
devices.push({
|
||||
serial,
|
||||
state,
|
||||
type,
|
||||
connection,
|
||||
properties,
|
||||
// We might not know these yet
|
||||
manufacturer: '',
|
||||
model: properties['model'] || '',
|
||||
product: properties['product'] || '',
|
||||
sdkVersion: '',
|
||||
});
|
||||
}
|
||||
else {
|
||||
debug('adb devices output line does not match expected regex: %O', line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
exports.parseAdbDevices = parseAdbDevices;
|
||||
async function forwardPorts(sdk, device, ports) {
|
||||
const debug = Debug(`${modulePrefix}:${forwardPorts.name}`);
|
||||
const args = ['-s', device.serial, 'reverse', `tcp:${ports.device}`, `tcp:${ports.host}`];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args, { timeout: 5000 });
|
||||
}
|
||||
exports.forwardPorts = forwardPorts;
|
||||
async function unforwardPorts(sdk, device, ports) {
|
||||
const debug = Debug(`${modulePrefix}:${unforwardPorts.name}`);
|
||||
const args = ['-s', device.serial, 'reverse', '--remove', `tcp:${ports.device}`];
|
||||
debug('Invoking adb with args: %O', args);
|
||||
await execAdb(sdk, args, { timeout: 5000 });
|
||||
}
|
||||
exports.unforwardPorts = unforwardPorts;
|
||||
async function execAdb(sdk, args, options = {}) {
|
||||
const debug = Debug(`${modulePrefix}:${execAdb.name}`);
|
||||
let timer;
|
||||
const retry = async () => {
|
||||
const msg = `ADBs is unresponsive after ${options.timeout}ms, killing server and retrying...\n`;
|
||||
if (process.argv.includes('--json')) {
|
||||
debug(msg);
|
||||
}
|
||||
else {
|
||||
process.stderr.write(msg);
|
||||
}
|
||||
debug('ADB timeout of %O reached, killing server and retrying...', options.timeout);
|
||||
debug('Invoking adb with args: %O', ['kill-server']);
|
||||
await execAdb(sdk, ['kill-server']);
|
||||
debug('Invoking adb with args: %O', ['start-server']);
|
||||
await execAdb(sdk, ['start-server']);
|
||||
debug('Retrying...');
|
||||
return run();
|
||||
};
|
||||
const run = async () => {
|
||||
const platformTools = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'platform-tools'));
|
||||
const adbBin = path.join(platformTools.location, 'adb');
|
||||
const { stdout } = await (0, process_1.execFile)(adbBin, args, {
|
||||
env: (0, sdk_1.supplementProcessEnv)(sdk),
|
||||
});
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = undefined;
|
||||
}
|
||||
return stdout;
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
if (options.timeout) {
|
||||
timer = setTimeout(() => retry().then(resolve, reject), options.timeout);
|
||||
}
|
||||
run().then(resolve, (err) => {
|
||||
if (!timer) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.execAdb = execAdb;
|
||||
36
node_modules/native-run/dist/android/utils/apk.js
generated
vendored
Normal file
36
node_modules/native-run/dist/android/utils/apk.js
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getApkInfo = exports.readAndroidManifest = void 0;
|
||||
const unzip_1 = require("../../utils/unzip");
|
||||
const binary_xml_parser_1 = require("./binary-xml-parser");
|
||||
async function readAndroidManifest(apkPath) {
|
||||
let error;
|
||||
const chunks = [];
|
||||
await (0, unzip_1.unzip)(apkPath, async (entry, zipfile, openReadStream) => {
|
||||
if (entry.fileName === 'AndroidManifest.xml') {
|
||||
const readStream = await openReadStream(entry);
|
||||
readStream.on('error', (err) => (error = err));
|
||||
readStream.on('data', (chunk) => chunks.push(chunk));
|
||||
readStream.on('end', () => zipfile.close());
|
||||
}
|
||||
else {
|
||||
zipfile.readEntry();
|
||||
}
|
||||
});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
const buf = Buffer.concat(chunks);
|
||||
const manifestBuffer = Buffer.from(buf);
|
||||
return new binary_xml_parser_1.BinaryXmlParser(manifestBuffer).parse();
|
||||
}
|
||||
exports.readAndroidManifest = readAndroidManifest;
|
||||
async function getApkInfo(apkPath) {
|
||||
const doc = await readAndroidManifest(apkPath);
|
||||
const appId = doc.attributes.find((a) => a.name === 'package').value;
|
||||
const application = doc.childNodes.find((n) => n.nodeName === 'application');
|
||||
const activity = application.childNodes.find((n) => n.nodeName === 'activity');
|
||||
const activityName = activity.attributes.find((a) => a.name === 'name').value;
|
||||
return { appId, activityName };
|
||||
}
|
||||
exports.getApkInfo = getApkInfo;
|
||||
69
node_modules/native-run/dist/android/utils/avd.js
generated
vendored
Normal file
69
node_modules/native-run/dist/android/utils/avd.js
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getInstalledAVDs = exports.getAVDFromINI = exports.getSDKVersionFromTarget = exports.getAVDFromConfigINI = exports.getAVDINIs = exports.isAVDConfigINI = exports.isAVDINI = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const pathlib = require("path");
|
||||
const ini_1 = require("../../utils/ini");
|
||||
const modulePrefix = 'native-run:android:utils:avd';
|
||||
const isAVDINI = (o) => o &&
|
||||
typeof o['avd.ini.encoding'] === 'string' &&
|
||||
typeof o['path'] === 'string' &&
|
||||
typeof o['path.rel'] === 'string' &&
|
||||
typeof o['target'] === 'string';
|
||||
exports.isAVDINI = isAVDINI;
|
||||
const isAVDConfigINI = (o) => o &&
|
||||
(typeof o['avd.ini.displayname'] === 'undefined' || typeof o['avd.ini.displayname'] === 'string') &&
|
||||
(typeof o['hw.lcd.density'] === 'undefined' || typeof o['hw.lcd.density'] === 'string') &&
|
||||
(typeof o['hw.lcd.height'] === 'undefined' || typeof o['hw.lcd.height'] === 'string') &&
|
||||
(typeof o['hw.lcd.width'] === 'undefined' || typeof o['hw.lcd.width'] === 'string') &&
|
||||
(typeof o['image.sysdir.1'] === 'undefined' || typeof o['image.sysdir.1'] === 'string');
|
||||
exports.isAVDConfigINI = isAVDConfigINI;
|
||||
async function getAVDINIs(sdk) {
|
||||
const debug = Debug(`${modulePrefix}:${getAVDINIs.name}`);
|
||||
const contents = await (0, utils_fs_1.readdir)(sdk.avdHome);
|
||||
const iniFilePaths = contents
|
||||
.filter((f) => pathlib.extname(f) === '.ini')
|
||||
.map((f) => pathlib.resolve(sdk.avdHome, f));
|
||||
debug('Discovered AVD ini files: %O', iniFilePaths);
|
||||
const iniFiles = await Promise.all(iniFilePaths.map(async (f) => [f, await (0, ini_1.readINI)(f, exports.isAVDINI)]));
|
||||
const avdInis = iniFiles.filter((c) => typeof c[1] !== 'undefined');
|
||||
return avdInis;
|
||||
}
|
||||
exports.getAVDINIs = getAVDINIs;
|
||||
function getAVDFromConfigINI(inipath, ini, configini) {
|
||||
const inibasename = pathlib.basename(inipath);
|
||||
const id = inibasename.substring(0, inibasename.length - pathlib.extname(inibasename).length);
|
||||
const name = configini['avd.ini.displayname'] ? String(configini['avd.ini.displayname']) : id.replace(/_/g, ' ');
|
||||
const screenDPI = configini['hw.lcd.density'] ? Number(configini['hw.lcd.density']) : null;
|
||||
const screenWidth = configini['hw.lcd.width'] ? Number(configini['hw.lcd.width']) : null;
|
||||
const screenHeight = configini['hw.lcd.height'] ? Number(configini['hw.lcd.height']) : null;
|
||||
return {
|
||||
id,
|
||||
path: ini.path,
|
||||
name,
|
||||
sdkVersion: getSDKVersionFromTarget(ini.target),
|
||||
screenDPI,
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
};
|
||||
}
|
||||
exports.getAVDFromConfigINI = getAVDFromConfigINI;
|
||||
function getSDKVersionFromTarget(target) {
|
||||
return target.replace(/^android-(\d+)/, '$1');
|
||||
}
|
||||
exports.getSDKVersionFromTarget = getSDKVersionFromTarget;
|
||||
async function getAVDFromINI(inipath, ini) {
|
||||
const configini = await (0, ini_1.readINI)(pathlib.resolve(ini.path, 'config.ini'), exports.isAVDConfigINI);
|
||||
if (configini) {
|
||||
return getAVDFromConfigINI(inipath, ini, configini);
|
||||
}
|
||||
}
|
||||
exports.getAVDFromINI = getAVDFromINI;
|
||||
async function getInstalledAVDs(sdk) {
|
||||
const avdInis = await getAVDINIs(sdk);
|
||||
const possibleAvds = await Promise.all(avdInis.map(([inipath, ini]) => getAVDFromINI(inipath, ini)));
|
||||
const avds = possibleAvds.filter((avd) => typeof avd !== 'undefined');
|
||||
return avds;
|
||||
}
|
||||
exports.getInstalledAVDs = getInstalledAVDs;
|
||||
593
node_modules/native-run/dist/android/utils/binary-xml-parser.js
generated
vendored
Normal file
593
node_modules/native-run/dist/android/utils/binary-xml-parser.js
generated
vendored
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinaryXmlParser = void 0;
|
||||
/*
|
||||
Copyright © 2013 CyberAgent, Inc.
|
||||
Copyright © 2016 The OpenSTF Project
|
||||
Modifications Copyright © 2018 Drifty Co
|
||||
|
||||
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.
|
||||
|
||||
https://github.com/openstf/adbkit-apkreader/blob/368f6b207c57e82fa7373c1608920ca7f4a8904c/lib/apkreader/parser/binaryxml.js
|
||||
*/
|
||||
const assert = require("assert");
|
||||
// import * as Debug from 'debug';
|
||||
const errors_1 = require("../../errors");
|
||||
// const debug = Debug('native-run:android:util:binary-xml-parser');
|
||||
const NodeType = {
|
||||
ELEMENT_NODE: 1,
|
||||
ATTRIBUTE_NODE: 2,
|
||||
CDATA_SECTION_NODE: 4,
|
||||
};
|
||||
const ChunkType = {
|
||||
NULL: 0x0000,
|
||||
STRING_POOL: 0x0001,
|
||||
TABLE: 0x0002,
|
||||
XML: 0x0003,
|
||||
XML_FIRST_CHUNK: 0x0100,
|
||||
XML_START_NAMESPACE: 0x0100,
|
||||
XML_END_NAMESPACE: 0x0101,
|
||||
XML_START_ELEMENT: 0x0102,
|
||||
XML_END_ELEMENT: 0x0103,
|
||||
XML_CDATA: 0x0104,
|
||||
XML_LAST_CHUNK: 0x017f,
|
||||
XML_RESOURCE_MAP: 0x0180,
|
||||
TABLE_PACKAGE: 0x0200,
|
||||
TABLE_TYPE: 0x0201,
|
||||
TABLE_TYPE_SPEC: 0x0202,
|
||||
};
|
||||
const StringFlags = {
|
||||
SORTED: 1 << 0,
|
||||
UTF8: 1 << 8,
|
||||
};
|
||||
// Taken from android.util.TypedValue
|
||||
const TypedValue = {
|
||||
COMPLEX_MANTISSA_MASK: 0x00ffffff,
|
||||
COMPLEX_MANTISSA_SHIFT: 0x00000008,
|
||||
COMPLEX_RADIX_0p23: 0x00000003,
|
||||
COMPLEX_RADIX_16p7: 0x00000001,
|
||||
COMPLEX_RADIX_23p0: 0x00000000,
|
||||
COMPLEX_RADIX_8p15: 0x00000002,
|
||||
COMPLEX_RADIX_MASK: 0x00000003,
|
||||
COMPLEX_RADIX_SHIFT: 0x00000004,
|
||||
COMPLEX_UNIT_DIP: 0x00000001,
|
||||
COMPLEX_UNIT_FRACTION: 0x00000000,
|
||||
COMPLEX_UNIT_FRACTION_PARENT: 0x00000001,
|
||||
COMPLEX_UNIT_IN: 0x00000004,
|
||||
COMPLEX_UNIT_MASK: 0x0000000f,
|
||||
COMPLEX_UNIT_MM: 0x00000005,
|
||||
COMPLEX_UNIT_PT: 0x00000003,
|
||||
COMPLEX_UNIT_PX: 0x00000000,
|
||||
COMPLEX_UNIT_SHIFT: 0x00000000,
|
||||
COMPLEX_UNIT_SP: 0x00000002,
|
||||
DENSITY_DEFAULT: 0x00000000,
|
||||
DENSITY_NONE: 0x0000ffff,
|
||||
TYPE_ATTRIBUTE: 0x00000002,
|
||||
TYPE_DIMENSION: 0x00000005,
|
||||
TYPE_FIRST_COLOR_INT: 0x0000001c,
|
||||
TYPE_FIRST_INT: 0x00000010,
|
||||
TYPE_FLOAT: 0x00000004,
|
||||
TYPE_FRACTION: 0x00000006,
|
||||
TYPE_INT_BOOLEAN: 0x00000012,
|
||||
TYPE_INT_COLOR_ARGB4: 0x0000001e,
|
||||
TYPE_INT_COLOR_ARGB8: 0x0000001c,
|
||||
TYPE_INT_COLOR_RGB4: 0x0000001f,
|
||||
TYPE_INT_COLOR_RGB8: 0x0000001d,
|
||||
TYPE_INT_DEC: 0x00000010,
|
||||
TYPE_INT_HEX: 0x00000011,
|
||||
TYPE_LAST_COLOR_INT: 0x0000001f,
|
||||
TYPE_LAST_INT: 0x0000001f,
|
||||
TYPE_NULL: 0x00000000,
|
||||
TYPE_REFERENCE: 0x00000001,
|
||||
TYPE_STRING: 0x00000003,
|
||||
};
|
||||
class BinaryXmlParser {
|
||||
constructor(buffer, options = {}) {
|
||||
this.buffer = buffer;
|
||||
this.cursor = 0;
|
||||
this.strings = [];
|
||||
this.resources = [];
|
||||
this.stack = [];
|
||||
this.debug = false;
|
||||
this.debug = options.debug || false;
|
||||
}
|
||||
readU8() {
|
||||
// debug('readU8');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer[this.cursor];
|
||||
// debug('value:', val);
|
||||
this.cursor += 1;
|
||||
return val;
|
||||
}
|
||||
readU16() {
|
||||
// debug('readU16');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer.readUInt16LE(this.cursor);
|
||||
// debug('value:', val);
|
||||
this.cursor += 2;
|
||||
return val;
|
||||
}
|
||||
readS32() {
|
||||
// debug('readS32');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer.readInt32LE(this.cursor);
|
||||
// debug('value:', val);
|
||||
this.cursor += 4;
|
||||
return val;
|
||||
}
|
||||
readU32() {
|
||||
// debug('readU32');
|
||||
// debug('cursor:', this.cursor);
|
||||
const val = this.buffer.readUInt32LE(this.cursor);
|
||||
// debug('value:', val);
|
||||
this.cursor += 4;
|
||||
return val;
|
||||
}
|
||||
readLength8() {
|
||||
// debug('readLength8');
|
||||
let len = this.readU8();
|
||||
if (len & 0x80) {
|
||||
len = (len & 0x7f) << 8;
|
||||
len += this.readU8();
|
||||
}
|
||||
// debug('length:', len);
|
||||
return len;
|
||||
}
|
||||
readLength16() {
|
||||
// debug('readLength16');
|
||||
let len = this.readU16();
|
||||
if (len & 0x8000) {
|
||||
len = (len & 0x7fff) << 16;
|
||||
len += this.readU16();
|
||||
}
|
||||
// debug('length:', len);
|
||||
return len;
|
||||
}
|
||||
readDimension() {
|
||||
// debug('readDimension');
|
||||
const dimension = {
|
||||
value: null,
|
||||
unit: null,
|
||||
rawUnit: null,
|
||||
};
|
||||
const value = this.readU32();
|
||||
const unit = dimension.value & 0xff;
|
||||
dimension.value = value >> 8;
|
||||
dimension.rawUnit = unit;
|
||||
switch (unit) {
|
||||
case TypedValue.COMPLEX_UNIT_MM:
|
||||
dimension.unit = 'mm';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_PX:
|
||||
dimension.unit = 'px';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_DIP:
|
||||
dimension.unit = 'dp';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_SP:
|
||||
dimension.unit = 'sp';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_PT:
|
||||
dimension.unit = 'pt';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_IN:
|
||||
dimension.unit = 'in';
|
||||
break;
|
||||
}
|
||||
return dimension;
|
||||
}
|
||||
readFraction() {
|
||||
// debug('readFraction');
|
||||
const fraction = {
|
||||
value: null,
|
||||
type: null,
|
||||
rawType: null,
|
||||
};
|
||||
const value = this.readU32();
|
||||
const type = value & 0xf;
|
||||
fraction.value = this.convertIntToFloat(value >> 4);
|
||||
fraction.rawType = type;
|
||||
switch (type) {
|
||||
case TypedValue.COMPLEX_UNIT_FRACTION:
|
||||
fraction.type = '%';
|
||||
break;
|
||||
case TypedValue.COMPLEX_UNIT_FRACTION_PARENT:
|
||||
fraction.type = '%p';
|
||||
break;
|
||||
}
|
||||
return fraction;
|
||||
}
|
||||
readHex24() {
|
||||
// debug('readHex24');
|
||||
const val = (this.readU32() & 0xffffff).toString(16);
|
||||
return val;
|
||||
}
|
||||
readHex32() {
|
||||
// debug('readHex32');
|
||||
const val = this.readU32().toString(16);
|
||||
return val;
|
||||
}
|
||||
readTypedValue() {
|
||||
// debug('readTypedValue');
|
||||
const typedValue = {
|
||||
value: null,
|
||||
type: null,
|
||||
rawType: null,
|
||||
};
|
||||
const start = this.cursor;
|
||||
let size = this.readU16();
|
||||
/* const zero = */ this.readU8();
|
||||
const dataType = this.readU8();
|
||||
// Yes, there has been a real world APK where the size is malformed.
|
||||
if (size === 0) {
|
||||
size = 8;
|
||||
}
|
||||
typedValue.rawType = dataType;
|
||||
switch (dataType) {
|
||||
case TypedValue.TYPE_INT_DEC:
|
||||
typedValue.value = this.readS32();
|
||||
typedValue.type = 'int_dec';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_HEX:
|
||||
typedValue.value = this.readS32();
|
||||
typedValue.type = 'int_hex';
|
||||
break;
|
||||
case TypedValue.TYPE_STRING: {
|
||||
const ref = this.readS32();
|
||||
typedValue.value = ref > 0 ? this.strings[ref] : '';
|
||||
typedValue.type = 'string';
|
||||
break;
|
||||
}
|
||||
case TypedValue.TYPE_REFERENCE: {
|
||||
const id = this.readU32();
|
||||
typedValue.value = `resourceId:0x${id.toString(16)}`;
|
||||
typedValue.type = 'reference';
|
||||
break;
|
||||
}
|
||||
case TypedValue.TYPE_INT_BOOLEAN:
|
||||
typedValue.value = this.readS32() !== 0;
|
||||
typedValue.type = 'boolean';
|
||||
break;
|
||||
case TypedValue.TYPE_NULL:
|
||||
this.readU32();
|
||||
typedValue.value = null;
|
||||
typedValue.type = 'null';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_RGB8:
|
||||
typedValue.value = this.readHex24();
|
||||
typedValue.type = 'rgb8';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_RGB4:
|
||||
typedValue.value = this.readHex24();
|
||||
typedValue.type = 'rgb4';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_ARGB8:
|
||||
typedValue.value = this.readHex32();
|
||||
typedValue.type = 'argb8';
|
||||
break;
|
||||
case TypedValue.TYPE_INT_COLOR_ARGB4:
|
||||
typedValue.value = this.readHex32();
|
||||
typedValue.type = 'argb4';
|
||||
break;
|
||||
case TypedValue.TYPE_DIMENSION:
|
||||
typedValue.value = this.readDimension();
|
||||
typedValue.type = 'dimension';
|
||||
break;
|
||||
case TypedValue.TYPE_FRACTION:
|
||||
typedValue.value = this.readFraction();
|
||||
typedValue.type = 'fraction';
|
||||
break;
|
||||
default: {
|
||||
// const type = dataType.toString(16);
|
||||
// debug(`Not sure what to do with typed value of type 0x${type}, falling back to reading an uint32.`);
|
||||
typedValue.value = this.readU32();
|
||||
typedValue.type = 'unknown';
|
||||
}
|
||||
}
|
||||
// Ensure we consume the whole value
|
||||
const end = start + size;
|
||||
if (this.cursor !== end) {
|
||||
// const type = dataType.toString(16);
|
||||
// const diff = end - this.cursor;
|
||||
// debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed end \
|
||||
// of typed value of type 0x${type}. The typed value started at offset ${start} \
|
||||
// and is supposed to end at offset ${end}. Ignoring the rest of the value.`);
|
||||
this.cursor = end;
|
||||
}
|
||||
return typedValue;
|
||||
}
|
||||
// https://twitter.com/kawasima/status/427730289201139712
|
||||
convertIntToFloat(int) {
|
||||
const buf = new ArrayBuffer(4);
|
||||
new Int32Array(buf)[0] = int;
|
||||
return new Float32Array(buf)[0];
|
||||
}
|
||||
readString(encoding) {
|
||||
// debug('readString', encoding);
|
||||
let stringLength;
|
||||
let byteLength;
|
||||
let value;
|
||||
switch (encoding) {
|
||||
case 'utf-8':
|
||||
stringLength = this.readLength8();
|
||||
// debug('stringLength:', stringLength);
|
||||
byteLength = this.readLength8();
|
||||
// debug('byteLength:', byteLength);
|
||||
value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength));
|
||||
// debug('value:', value);
|
||||
assert.equal(this.readU8(), 0, 'String must end with trailing zero');
|
||||
return value;
|
||||
case 'ucs2':
|
||||
stringLength = this.readLength16();
|
||||
// debug('stringLength:', stringLength);
|
||||
byteLength = stringLength * 2;
|
||||
// debug('byteLength:', byteLength);
|
||||
value = this.buffer.toString(encoding, this.cursor, (this.cursor += byteLength));
|
||||
// debug('value:', value);
|
||||
assert.equal(this.readU16(), 0, 'String must end with trailing zero');
|
||||
return value;
|
||||
default:
|
||||
throw new errors_1.Exception(`Unsupported encoding '${encoding}'`);
|
||||
}
|
||||
}
|
||||
readChunkHeader() {
|
||||
// debug('readChunkHeader');
|
||||
const header = {
|
||||
startOffset: this.cursor,
|
||||
chunkType: this.readU16(),
|
||||
headerSize: this.readU16(),
|
||||
chunkSize: this.readU32(),
|
||||
};
|
||||
// debug('startOffset:', header.startOffset);
|
||||
// debug('chunkType:', header.chunkType);
|
||||
// debug('headerSize:', header.headerSize);
|
||||
// debug('chunkSize:', header.chunkSize);
|
||||
return header;
|
||||
}
|
||||
readStringPool(header) {
|
||||
// debug('readStringPool');
|
||||
header.stringCount = this.readU32();
|
||||
// debug('stringCount:', header.stringCount);
|
||||
header.styleCount = this.readU32();
|
||||
// debug('styleCount:', header.styleCount);
|
||||
header.flags = this.readU32();
|
||||
// debug('flags:', header.flags);
|
||||
header.stringsStart = this.readU32();
|
||||
// debug('stringsStart:', header.stringsStart);
|
||||
header.stylesStart = this.readU32();
|
||||
// debug('stylesStart:', header.stylesStart);
|
||||
if (header.chunkType !== ChunkType.STRING_POOL) {
|
||||
throw new errors_1.Exception('Invalid string pool header');
|
||||
}
|
||||
const offsets = [];
|
||||
for (let i = 0, l = header.stringCount; i < l; ++i) {
|
||||
// debug('offset:', i);
|
||||
offsets.push(this.readU32());
|
||||
}
|
||||
// const sorted = (header.flags & StringFlags.SORTED) === StringFlags.SORTED;
|
||||
// debug('sorted:', sorted);
|
||||
const encoding = (header.flags & StringFlags.UTF8) === StringFlags.UTF8 ? 'utf-8' : 'ucs2';
|
||||
// debug('encoding:', encoding);
|
||||
const stringsStart = header.startOffset + header.stringsStart;
|
||||
this.cursor = stringsStart;
|
||||
for (let i = 0, l = header.stringCount; i < l; ++i) {
|
||||
// debug('string:', i);
|
||||
// debug('offset:', offsets[i]);
|
||||
this.cursor = stringsStart + offsets[i];
|
||||
this.strings.push(this.readString(encoding));
|
||||
}
|
||||
// Skip styles
|
||||
this.cursor = header.startOffset + header.chunkSize;
|
||||
return null;
|
||||
}
|
||||
readResourceMap(header) {
|
||||
// debug('readResourceMap');
|
||||
const count = Math.floor((header.chunkSize - header.headerSize) / 4);
|
||||
for (let i = 0; i < count; ++i) {
|
||||
this.resources.push(this.readU32());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
readXmlNamespaceStart( /* header */) {
|
||||
// debug('readXmlNamespaceStart');
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
// const prefixRef = this.readS32();
|
||||
// const uriRef = this.readS32();
|
||||
// We don't currently care about the values, but they could
|
||||
// be accessed like so:
|
||||
//
|
||||
// namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0
|
||||
// namespaceURI.uri = this.strings[uriRef] // if uriRef > 0
|
||||
return null;
|
||||
}
|
||||
readXmlNamespaceEnd( /* header */) {
|
||||
// debug('readXmlNamespaceEnd');
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
// const prefixRef = this.readS32();
|
||||
// const uriRef = this.readS32();
|
||||
// We don't currently care about the values, but they could
|
||||
// be accessed like so:
|
||||
//
|
||||
// namespaceURI.prefix = this.strings[prefixRef] // if prefixRef > 0
|
||||
// namespaceURI.uri = this.strings[uriRef] // if uriRef > 0
|
||||
return null;
|
||||
}
|
||||
readXmlElementStart( /* header */) {
|
||||
// debug('readXmlElementStart');
|
||||
const node = {
|
||||
namespaceURI: null,
|
||||
nodeType: NodeType.ELEMENT_NODE,
|
||||
nodeName: null,
|
||||
attributes: [],
|
||||
childNodes: [],
|
||||
};
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
const nsRef = this.readS32();
|
||||
const nameRef = this.readS32();
|
||||
if (nsRef > 0) {
|
||||
node.namespaceURI = this.strings[nsRef];
|
||||
}
|
||||
node.nodeName = this.strings[nameRef];
|
||||
this.readU16();
|
||||
this.readU16();
|
||||
// const attrStart = this.readU16();
|
||||
// const attrSize = this.readU16();
|
||||
const attrCount = this.readU16();
|
||||
// const idIndex = this.readU16();
|
||||
// const classIndex = this.readU16();
|
||||
// const styleIndex = this.readU16();
|
||||
this.readU16();
|
||||
this.readU16();
|
||||
this.readU16();
|
||||
for (let i = 0; i < attrCount; ++i) {
|
||||
node.attributes.push(this.readXmlAttribute());
|
||||
}
|
||||
if (this.document) {
|
||||
this.parent.childNodes.push(node);
|
||||
this.parent = node;
|
||||
}
|
||||
else {
|
||||
this.document = this.parent = node;
|
||||
}
|
||||
this.stack.push(node);
|
||||
return node;
|
||||
}
|
||||
readXmlAttribute() {
|
||||
// debug('readXmlAttribute');
|
||||
const attr = {
|
||||
namespaceURI: null,
|
||||
nodeType: NodeType.ATTRIBUTE_NODE,
|
||||
nodeName: null,
|
||||
name: null,
|
||||
value: null,
|
||||
typedValue: null,
|
||||
};
|
||||
const nsRef = this.readS32();
|
||||
const nameRef = this.readS32();
|
||||
const valueRef = this.readS32();
|
||||
if (nsRef > 0) {
|
||||
attr.namespaceURI = this.strings[nsRef];
|
||||
}
|
||||
attr.nodeName = attr.name = this.strings[nameRef];
|
||||
if (valueRef > 0) {
|
||||
attr.value = this.strings[valueRef];
|
||||
}
|
||||
attr.typedValue = this.readTypedValue();
|
||||
return attr;
|
||||
}
|
||||
readXmlElementEnd( /* header */) {
|
||||
// debug('readXmlCData');
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
// const nsRef = this.readS32();
|
||||
// const nameRef = this.readS32();
|
||||
this.stack.pop();
|
||||
this.parent = this.stack[this.stack.length - 1];
|
||||
return null;
|
||||
}
|
||||
readXmlCData( /* header */) {
|
||||
// debug('readXmlCData');
|
||||
const cdata = {
|
||||
namespaceURI: null,
|
||||
nodeType: NodeType.CDATA_SECTION_NODE,
|
||||
nodeName: '#cdata',
|
||||
data: null,
|
||||
typedValue: null,
|
||||
};
|
||||
this.readU32();
|
||||
this.readU32();
|
||||
// const line = this.readU32();
|
||||
// const commentRef = this.readU32();
|
||||
const dataRef = this.readS32();
|
||||
if (dataRef > 0) {
|
||||
cdata.data = this.strings[dataRef];
|
||||
}
|
||||
cdata.typedValue = this.readTypedValue();
|
||||
this.parent.childNodes.push(cdata);
|
||||
return cdata;
|
||||
}
|
||||
readNull(header) {
|
||||
// debug('readNull');
|
||||
this.cursor += header.chunkSize - header.headerSize;
|
||||
return null;
|
||||
}
|
||||
parse() {
|
||||
// debug('parse');
|
||||
const xmlHeader = this.readChunkHeader();
|
||||
if (xmlHeader.chunkType !== ChunkType.XML) {
|
||||
throw new errors_1.Exception('Invalid XML header');
|
||||
}
|
||||
while (this.cursor < this.buffer.length) {
|
||||
// debug('chunk');
|
||||
const start = this.cursor;
|
||||
const header = this.readChunkHeader();
|
||||
switch (header.chunkType) {
|
||||
case ChunkType.STRING_POOL:
|
||||
this.readStringPool(header);
|
||||
break;
|
||||
case ChunkType.XML_RESOURCE_MAP:
|
||||
this.readResourceMap(header);
|
||||
break;
|
||||
case ChunkType.XML_START_NAMESPACE:
|
||||
this.readXmlNamespaceStart();
|
||||
break;
|
||||
case ChunkType.XML_END_NAMESPACE:
|
||||
this.readXmlNamespaceEnd();
|
||||
break;
|
||||
case ChunkType.XML_START_ELEMENT:
|
||||
this.readXmlElementStart();
|
||||
break;
|
||||
case ChunkType.XML_END_ELEMENT:
|
||||
this.readXmlElementEnd();
|
||||
break;
|
||||
case ChunkType.XML_CDATA:
|
||||
this.readXmlCData();
|
||||
break;
|
||||
case ChunkType.NULL:
|
||||
this.readNull(header);
|
||||
break;
|
||||
default:
|
||||
throw new errors_1.Exception(`Unsupported chunk type '${header.chunkType}'`);
|
||||
}
|
||||
// Ensure we consume the whole chunk
|
||||
const end = start + header.chunkSize;
|
||||
if (this.cursor !== end) {
|
||||
// const diff = end - this.cursor;
|
||||
// const type = header.chunkType.toString(16);
|
||||
// debug(`Cursor is off by ${diff} bytes at ${this.cursor} at supposed \
|
||||
// end of chunk of type 0x${type}. The chunk started at offset ${start} and is \
|
||||
// supposed to end at offset ${end}. Ignoring the rest of the chunk.`);
|
||||
// this.cursor = end;
|
||||
}
|
||||
}
|
||||
return this.document;
|
||||
}
|
||||
}
|
||||
exports.BinaryXmlParser = BinaryXmlParser;
|
||||
204
node_modules/native-run/dist/android/utils/emulator.js
generated
vendored
Normal file
204
node_modules/native-run/dist/android/utils/emulator.js
generated
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseAndroidConsoleResponse = exports.getAVDFromEmulator = exports.parseEmulatorOutput = exports.EmulatorEvent = exports.spawnEmulator = exports.runEmulator = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const child_process_1 = require("child_process");
|
||||
const Debug = require("debug");
|
||||
const net = require("net");
|
||||
const os = require("os");
|
||||
const path = require("path");
|
||||
const split2 = require("split2");
|
||||
const through2 = require("through2");
|
||||
const errors_1 = require("../../errors");
|
||||
const fn_1 = require("../../utils/fn");
|
||||
const adb_1 = require("./adb");
|
||||
const sdk_1 = require("./sdk");
|
||||
const modulePrefix = 'native-run:android:utils:emulator';
|
||||
/**
|
||||
* Resolves when emulator is ready and running with the specified AVD.
|
||||
*/
|
||||
async function runEmulator(sdk, avd, port) {
|
||||
try {
|
||||
await spawnEmulator(sdk, avd, port);
|
||||
}
|
||||
catch (e) {
|
||||
if (!(e instanceof errors_1.EmulatorException) || e.code !== errors_1.ERR_ALREADY_RUNNING) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
const serial = `emulator-${port}`;
|
||||
const devices = await (0, adb_1.getDevices)(sdk);
|
||||
const emulator = devices.find((device) => device.serial === serial);
|
||||
if (!emulator) {
|
||||
throw new errors_1.EmulatorException(`Emulator not found: ${serial}`);
|
||||
}
|
||||
return emulator;
|
||||
}
|
||||
exports.runEmulator = runEmulator;
|
||||
async function spawnEmulator(sdk, avd, port) {
|
||||
const debug = Debug(`${modulePrefix}:${spawnEmulator.name}`);
|
||||
const emulator = await (0, sdk_1.getSDKPackage)(path.join(sdk.root, 'emulator'));
|
||||
const emulatorBin = path.join(emulator.location, 'emulator');
|
||||
const args = ['-avd', avd.id, '-port', port.toString(), '-verbose'];
|
||||
debug('Invoking emulator: %O %O', emulatorBin, args);
|
||||
const p = (0, child_process_1.spawn)(emulatorBin, args, {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: (0, sdk_1.supplementProcessEnv)(sdk),
|
||||
});
|
||||
p.unref();
|
||||
return new Promise((_resolve, _reject) => {
|
||||
const resolve = (0, fn_1.once)(() => {
|
||||
_resolve();
|
||||
cleanup();
|
||||
});
|
||||
const reject = (0, fn_1.once)((err) => {
|
||||
_reject(err);
|
||||
cleanup();
|
||||
});
|
||||
(0, adb_1.waitForDevice)(sdk, `emulator-${port}`).then(() => resolve(), (err) => reject(err));
|
||||
const eventParser = through2((chunk, enc, cb) => {
|
||||
const line = chunk.toString();
|
||||
debug('Android Emulator: %O', line);
|
||||
const event = parseEmulatorOutput(line);
|
||||
if (event === EmulatorEvent.AlreadyRunning) {
|
||||
reject(new errors_1.EmulatorException(`Emulator already running with AVD [${avd.id}]`, errors_1.ERR_ALREADY_RUNNING));
|
||||
}
|
||||
else if (event === EmulatorEvent.UnknownAVD) {
|
||||
reject(new errors_1.EmulatorException(`Unknown AVD name [${avd.id}]`, errors_1.ERR_UNKNOWN_AVD));
|
||||
}
|
||||
else if (event === EmulatorEvent.AVDHomeNotFound) {
|
||||
reject(new errors_1.EmulatorException(`Emulator cannot find AVD home`, errors_1.ERR_AVD_HOME_NOT_FOUND));
|
||||
}
|
||||
cb();
|
||||
});
|
||||
const stdoutStream = p.stdout.pipe(split2());
|
||||
const stderrStream = p.stderr.pipe(split2());
|
||||
stdoutStream.pipe(eventParser);
|
||||
stderrStream.pipe(eventParser);
|
||||
const cleanup = () => {
|
||||
debug('Unhooking stdout/stderr streams from emulator process');
|
||||
p.stdout.push(null);
|
||||
p.stderr.push(null);
|
||||
};
|
||||
p.on('close', (code) => {
|
||||
debug('Emulator closed, exit code %d', code);
|
||||
if (code) {
|
||||
reject(new errors_1.EmulatorException(`Non-zero exit code from Emulator: ${code}`, errors_1.ERR_NON_ZERO_EXIT));
|
||||
}
|
||||
});
|
||||
p.on('error', (err) => {
|
||||
debug('Emulator error: %O', err);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.spawnEmulator = spawnEmulator;
|
||||
var EmulatorEvent;
|
||||
(function (EmulatorEvent) {
|
||||
EmulatorEvent[EmulatorEvent["UnknownAVD"] = 0] = "UnknownAVD";
|
||||
EmulatorEvent[EmulatorEvent["AlreadyRunning"] = 1] = "AlreadyRunning";
|
||||
EmulatorEvent[EmulatorEvent["AVDHomeNotFound"] = 2] = "AVDHomeNotFound";
|
||||
})(EmulatorEvent = exports.EmulatorEvent || (exports.EmulatorEvent = {}));
|
||||
function parseEmulatorOutput(line) {
|
||||
const debug = Debug(`${modulePrefix}:${parseEmulatorOutput.name}`);
|
||||
let event;
|
||||
if (line.includes('Unknown AVD name')) {
|
||||
event = EmulatorEvent.UnknownAVD;
|
||||
}
|
||||
else if (line.includes('another emulator instance running with the current AVD')) {
|
||||
event = EmulatorEvent.AlreadyRunning;
|
||||
}
|
||||
else if (line.includes('Cannot find AVD system path')) {
|
||||
event = EmulatorEvent.AVDHomeNotFound;
|
||||
}
|
||||
if (typeof event !== 'undefined') {
|
||||
debug('Parsed event from emulator output: %s', EmulatorEvent[event]);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
exports.parseEmulatorOutput = parseEmulatorOutput;
|
||||
async function getAVDFromEmulator(emulator, avds) {
|
||||
const debug = Debug(`${modulePrefix}:${getAVDFromEmulator.name}`);
|
||||
const emulatorPortRegex = /^emulator-(\d+)$/;
|
||||
const m = emulator.serial.match(emulatorPortRegex);
|
||||
if (!m) {
|
||||
throw new errors_1.EmulatorException(`Emulator ${emulator.serial} does not match expected emulator serial format`);
|
||||
}
|
||||
const port = Number.parseInt(m[1], 10);
|
||||
const host = 'localhost';
|
||||
const sock = net.createConnection({ host, port });
|
||||
sock.setEncoding('utf8');
|
||||
sock.setTimeout(5000);
|
||||
const readAuthFile = new Promise((resolve, reject) => {
|
||||
sock.on('connect', () => {
|
||||
debug('Connected to %s:%d', host, port);
|
||||
(0, utils_fs_1.readFile)(path.resolve(os.homedir(), '.emulator_console_auth_token'), {
|
||||
encoding: 'utf8',
|
||||
}).then((contents) => resolve(contents.trim()), (err) => reject(err));
|
||||
});
|
||||
});
|
||||
let Stage;
|
||||
(function (Stage) {
|
||||
Stage[Stage["Initial"] = 0] = "Initial";
|
||||
Stage[Stage["Auth"] = 1] = "Auth";
|
||||
Stage[Stage["AuthSuccess"] = 2] = "AuthSuccess";
|
||||
Stage[Stage["Response"] = 3] = "Response";
|
||||
Stage[Stage["Complete"] = 4] = "Complete";
|
||||
})(Stage || (Stage = {}));
|
||||
return new Promise((resolve, reject) => {
|
||||
let stage = Stage.Initial;
|
||||
const timer = setTimeout(() => {
|
||||
if (stage !== Stage.Complete) {
|
||||
reject(new errors_1.EmulatorException(`Took too long to get AVD name from Android Emulator Console, something went wrong.`));
|
||||
}
|
||||
}, 3000);
|
||||
const cleanup = (0, fn_1.once)(() => {
|
||||
clearTimeout(timer);
|
||||
sock.end();
|
||||
});
|
||||
sock.on('timeout', () => {
|
||||
reject(new errors_1.EmulatorException(`Socket timeout on ${host}:${port}`));
|
||||
cleanup();
|
||||
});
|
||||
sock.pipe(split2()).pipe(through2((chunk, enc, cb) => {
|
||||
const line = chunk.toString();
|
||||
debug('Android Console: %O', line);
|
||||
if (stage === Stage.Initial && line.includes('Authentication required')) {
|
||||
stage = Stage.Auth;
|
||||
}
|
||||
else if (stage === Stage.Auth && line.trim() === 'OK') {
|
||||
readAuthFile.then((token) => sock.write(`auth ${token}\n`, 'utf8'), (err) => reject(err));
|
||||
stage = Stage.AuthSuccess;
|
||||
}
|
||||
else if (stage === Stage.AuthSuccess && line.trim() === 'OK') {
|
||||
sock.write('avd name\n', 'utf8');
|
||||
stage = Stage.Response;
|
||||
}
|
||||
else if (stage === Stage.Response) {
|
||||
const avdId = line.trim();
|
||||
const avd = avds.find((avd) => avd.id === avdId);
|
||||
if (avd) {
|
||||
resolve(avd);
|
||||
}
|
||||
else {
|
||||
reject(new errors_1.EmulatorException(`Unknown AVD name [${avdId}]`, errors_1.ERR_UNKNOWN_AVD));
|
||||
}
|
||||
stage = Stage.Complete;
|
||||
cleanup();
|
||||
}
|
||||
cb();
|
||||
}));
|
||||
});
|
||||
}
|
||||
exports.getAVDFromEmulator = getAVDFromEmulator;
|
||||
function parseAndroidConsoleResponse(output) {
|
||||
const debug = Debug(`${modulePrefix}:${parseAndroidConsoleResponse.name}`);
|
||||
const m = /([\s\S]+)OK\r?\n/g.exec(output);
|
||||
if (m) {
|
||||
const [, response] = m;
|
||||
debug('Parsed response data from Android Console output: %O', response);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
exports.parseAndroidConsoleResponse = parseAndroidConsoleResponse;
|
||||
32
node_modules/native-run/dist/android/utils/list.js
generated
vendored
Normal file
32
node_modules/native-run/dist/android/utils/list.js
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.avdToTarget = exports.deviceToTarget = exports.getVirtualTargets = exports.getDeviceTargets = void 0;
|
||||
const adb_1 = require("./adb");
|
||||
const avd_1 = require("./avd");
|
||||
async function getDeviceTargets(sdk) {
|
||||
return (await (0, adb_1.getDevices)(sdk)).filter((device) => device.type === 'hardware').map(deviceToTarget);
|
||||
}
|
||||
exports.getDeviceTargets = getDeviceTargets;
|
||||
async function getVirtualTargets(sdk) {
|
||||
const avds = await (0, avd_1.getInstalledAVDs)(sdk);
|
||||
return avds.map(avdToTarget);
|
||||
}
|
||||
exports.getVirtualTargets = getVirtualTargets;
|
||||
function deviceToTarget(device) {
|
||||
return {
|
||||
platform: 'android',
|
||||
model: `${device.manufacturer} ${device.model}`,
|
||||
sdkVersion: device.sdkVersion,
|
||||
id: device.serial,
|
||||
};
|
||||
}
|
||||
exports.deviceToTarget = deviceToTarget;
|
||||
function avdToTarget(avd) {
|
||||
return {
|
||||
platform: 'android',
|
||||
name: avd.name,
|
||||
sdkVersion: avd.sdkVersion,
|
||||
id: avd.id,
|
||||
};
|
||||
}
|
||||
exports.avdToTarget = avdToTarget;
|
||||
132
node_modules/native-run/dist/android/utils/run.js
generated
vendored
Normal file
132
node_modules/native-run/dist/android/utils/run.js
generated
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.installApkToDevice = exports.selectVirtualDevice = exports.selectHardwareDevice = exports.selectDeviceByTarget = exports.isLikelyEmulator = exports.findAvailableEmulatorPort = void 0;
|
||||
const Debug = require("debug");
|
||||
const errors_1 = require("../../errors");
|
||||
const log_1 = require("../../utils/log");
|
||||
const adb_1 = require("./adb");
|
||||
const emulator_1 = require("./emulator");
|
||||
const modulePrefix = 'native-run:android:utils:run';
|
||||
async function findAvailableEmulatorPort(devices, start = 5554, end = 5584) {
|
||||
const debug = Debug(`${modulePrefix}:${findAvailableEmulatorPort.name}`);
|
||||
const usedPorts = new Set();
|
||||
for (const d of devices) {
|
||||
const m = d.serial.match(/^emulator-(\d+)$/);
|
||||
if (m) {
|
||||
usedPorts.add(Number(m[1]));
|
||||
}
|
||||
}
|
||||
for (let port = start; port <= end; port += 2) {
|
||||
if (!usedPorts.has(port)) {
|
||||
debug('Available emulator port found: %d', port);
|
||||
return port;
|
||||
}
|
||||
}
|
||||
debug('No available emulator ports found in range %d-%d; defaulting to 5554', start, end);
|
||||
return 5554;
|
||||
}
|
||||
exports.findAvailableEmulatorPort = findAvailableEmulatorPort;
|
||||
function isLikelyEmulator(device) {
|
||||
const serialEmu = /^emulator-(\d+)$/;
|
||||
if (serialEmu.test(device.serial)) {
|
||||
return true;
|
||||
}
|
||||
if (device.type === 'emulator') {
|
||||
return true;
|
||||
}
|
||||
const props = device.properties || {};
|
||||
const deviceProp = (props['device'] || '').toLowerCase();
|
||||
const productProp = (props['product'] || '').toLowerCase();
|
||||
const model = (device.model || '').toLowerCase();
|
||||
if (deviceProp.startsWith('emu') || deviceProp.includes('generic')) {
|
||||
return true;
|
||||
}
|
||||
if (productProp.includes('sdk_gphone') || productProp.includes('google_sdk')) {
|
||||
return true;
|
||||
}
|
||||
if (model.includes('android_sdk') || model.includes('sdk_gphone')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
exports.isLikelyEmulator = isLikelyEmulator;
|
||||
async function selectDeviceByTarget(sdk, devices, avds, target) {
|
||||
const debug = Debug(`${modulePrefix}:${selectDeviceByTarget.name}`);
|
||||
debug('--target %s detected', target);
|
||||
debug('Checking if device can be found by serial: %s', target);
|
||||
const device = devices.find((d) => d.serial === target);
|
||||
if (device) {
|
||||
debug('Device found by serial: %s', device.serial);
|
||||
return device;
|
||||
}
|
||||
const emulatorDevices = devices.filter(isLikelyEmulator);
|
||||
const pairAVD = async (emulator) => {
|
||||
let avd;
|
||||
try {
|
||||
avd = await (0, emulator_1.getAVDFromEmulator)(emulator, avds);
|
||||
debug('Emulator %s is using AVD: %s', emulator.serial, avd.id);
|
||||
}
|
||||
catch (e) {
|
||||
debug('Error with emulator %s: %O', emulator.serial, e);
|
||||
}
|
||||
return [emulator, avd];
|
||||
};
|
||||
debug('Checking if any of %d running emulators are using AVD by ID: %s', emulatorDevices.length, target);
|
||||
const emulatorsAndAVDs = await Promise.all(emulatorDevices.map((emulator) => pairAVD(emulator)));
|
||||
const emulators = emulatorsAndAVDs.filter((t) => typeof t[1] !== 'undefined');
|
||||
const emulator = emulators.find(([, avd]) => avd.id === target);
|
||||
if (emulator) {
|
||||
const [device, avd] = emulator;
|
||||
debug('Emulator %s found by AVD: %s', device.serial, avd.id);
|
||||
return device;
|
||||
}
|
||||
debug('Checking if AVD can be found by ID: %s', target);
|
||||
const avd = avds.find((avd) => avd.id === target);
|
||||
if (avd) {
|
||||
debug('AVD found by ID: %s', avd.id);
|
||||
const port = await findAvailableEmulatorPort(devices);
|
||||
debug('Using emulator port: %d', port);
|
||||
const device = await (0, emulator_1.runEmulator)(sdk, avd, port);
|
||||
debug('Emulator ready, running avd: %s on %s', avd.id, device.serial);
|
||||
return device;
|
||||
}
|
||||
}
|
||||
exports.selectDeviceByTarget = selectDeviceByTarget;
|
||||
async function selectHardwareDevice(devices) {
|
||||
const hardwareDevices = devices.filter((d) => d.type === 'hardware');
|
||||
// If a hardware device is found, we prefer launching to it instead of in an emulator.
|
||||
if (hardwareDevices.length > 0) {
|
||||
return hardwareDevices[0]; // TODO: can probably do better analysis on which to use?
|
||||
}
|
||||
}
|
||||
exports.selectHardwareDevice = selectHardwareDevice;
|
||||
async function selectVirtualDevice(sdk, devices, avds) {
|
||||
const debug = Debug(`${modulePrefix}:${selectVirtualDevice.name}`);
|
||||
const emulators = devices.filter((d) => d.type === 'emulator');
|
||||
// If an emulator is running, use it.
|
||||
if (emulators.length > 0) {
|
||||
const [emulator] = emulators;
|
||||
debug('Found running emulator: %s', emulator.serial);
|
||||
return emulator;
|
||||
}
|
||||
throw new errors_1.AndroidRunException('No target devices/emulators available.', errors_1.ERR_NO_TARGET);
|
||||
}
|
||||
exports.selectVirtualDevice = selectVirtualDevice;
|
||||
async function installApkToDevice(sdk, device, apk, appId) {
|
||||
(0, log_1.log)(`Installing ${apk}...\n`);
|
||||
try {
|
||||
await (0, adb_1.installApk)(sdk, device, apk);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof errors_1.ADBException) {
|
||||
if (e.code === errors_1.ERR_INCOMPATIBLE_UPDATE || e.code === errors_1.ERR_VERSION_DOWNGRADE) {
|
||||
(0, log_1.log)(`${e.message} Uninstalling and trying again...\n`);
|
||||
await (0, adb_1.uninstallApp)(sdk, device, appId);
|
||||
await (0, adb_1.installApk)(sdk, device, apk);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
exports.installApkToDevice = installApkToDevice;
|
||||
47
node_modules/native-run/dist/android/utils/sdk/api.js
generated
vendored
Normal file
47
node_modules/native-run/dist/android/utils/sdk/api.js
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.findPackageBySchemaPath = exports.findPackageBySchema = exports.findUnsatisfiedPackages = exports.getAPILevels = void 0;
|
||||
const Debug = require("debug");
|
||||
const modulePrefix = 'native-run:android:utils:sdk:api';
|
||||
async function getAPILevels(packages) {
|
||||
const debug = Debug(`${modulePrefix}:${getAPILevels.name}`);
|
||||
const levels = [
|
||||
...new Set(packages.map((pkg) => pkg.apiLevel).filter((apiLevel) => typeof apiLevel !== 'undefined')),
|
||||
].sort((a, b) => (a <= b ? 1 : -1));
|
||||
const apis = levels.map((apiLevel) => ({
|
||||
apiLevel,
|
||||
packages: packages.filter((pkg) => pkg.apiLevel === apiLevel),
|
||||
}));
|
||||
debug('Discovered installed API Levels: %O', apis.map((api) => ({ ...api, packages: api.packages.map((pkg) => pkg.path) })));
|
||||
return apis;
|
||||
}
|
||||
exports.getAPILevels = getAPILevels;
|
||||
function findUnsatisfiedPackages(packages, schemas) {
|
||||
return schemas.filter((pkg) => !findPackageBySchema(packages, pkg));
|
||||
}
|
||||
exports.findUnsatisfiedPackages = findUnsatisfiedPackages;
|
||||
function findPackageBySchema(packages, pkg) {
|
||||
const apiPkg = findPackageBySchemaPath(packages, pkg.path);
|
||||
if (apiPkg) {
|
||||
if (typeof pkg.version === 'string') {
|
||||
if (pkg.version === apiPkg.version) {
|
||||
return apiPkg;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (apiPkg.version.match(pkg.version)) {
|
||||
return apiPkg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.findPackageBySchema = findPackageBySchema;
|
||||
function findPackageBySchemaPath(packages, path) {
|
||||
return packages.find((pkg) => {
|
||||
if (typeof path !== 'string') {
|
||||
return !!pkg.path.match(path);
|
||||
}
|
||||
return path === pkg.path;
|
||||
});
|
||||
}
|
||||
exports.findPackageBySchemaPath = findPackageBySchemaPath;
|
||||
169
node_modules/native-run/dist/android/utils/sdk/index.js
generated
vendored
Normal file
169
node_modules/native-run/dist/android/utils/sdk/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.supplementProcessEnv = exports.resolveAVDHome = exports.resolveEmulatorHome = exports.resolveSDKRoot = exports.getSDKPackage = exports.findAllSDKPackages = exports.getSDK = exports.SDK_DIRECTORIES = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const Debug = require("debug");
|
||||
const os = require("os");
|
||||
const pathlib = require("path");
|
||||
const errors_1 = require("../../../errors");
|
||||
const fs_1 = require("../../../utils/fs");
|
||||
const xml_1 = require("./xml");
|
||||
const modulePrefix = 'native-run:android:utils:sdk';
|
||||
const homedir = os.homedir();
|
||||
exports.SDK_DIRECTORIES = new Map([
|
||||
['darwin', [pathlib.join(homedir, 'Library', 'Android', 'sdk')]],
|
||||
['linux', [pathlib.join(homedir, 'Android', 'sdk')]],
|
||||
['win32', [pathlib.join(process.env.LOCALAPPDATA || pathlib.join(homedir, 'AppData', 'Local'), 'Android', 'Sdk')]],
|
||||
]);
|
||||
async function getSDK() {
|
||||
const root = await resolveSDKRoot();
|
||||
const emulatorHome = await resolveEmulatorHome();
|
||||
const avdHome = await resolveAVDHome();
|
||||
return { root, emulatorHome, avdHome };
|
||||
}
|
||||
exports.getSDK = getSDK;
|
||||
const pkgcache = new Map();
|
||||
async function findAllSDKPackages(sdk) {
|
||||
const debug = Debug(`${modulePrefix}:${findAllSDKPackages.name}`);
|
||||
if (sdk.packages) {
|
||||
return sdk.packages;
|
||||
}
|
||||
const sourcesRe = /^sources\/android-\d+\/.+\/.+/;
|
||||
debug('Walking %s to discover SDK packages', sdk.root);
|
||||
const contents = await (0, utils_fs_1.readdirp)(sdk.root, {
|
||||
filter: (item) => pathlib.basename(item.path) === 'package.xml',
|
||||
onError: (err) => debug('Error while walking SDK: %O', err),
|
||||
walkerOptions: {
|
||||
pathFilter: (p) => {
|
||||
if ([
|
||||
'bin',
|
||||
'bin64',
|
||||
'lib',
|
||||
'lib64',
|
||||
'include',
|
||||
'clang-include',
|
||||
'skins',
|
||||
'data',
|
||||
'examples',
|
||||
'resources',
|
||||
'systrace',
|
||||
'extras',
|
||||
// 'm2repository',
|
||||
].includes(pathlib.basename(p))) {
|
||||
return false;
|
||||
}
|
||||
if (p.match(sourcesRe)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
sdk.packages = await Promise.all(contents.map((p) => pathlib.dirname(p)).map((p) => getSDKPackage(p)));
|
||||
sdk.packages.sort((a, b) => (a.name >= b.name ? 1 : -1));
|
||||
return sdk.packages;
|
||||
}
|
||||
exports.findAllSDKPackages = findAllSDKPackages;
|
||||
async function getSDKPackage(location) {
|
||||
const debug = Debug(`${modulePrefix}:${getSDKPackage.name}`);
|
||||
let pkg = pkgcache.get(location);
|
||||
if (!pkg) {
|
||||
const packageXmlPath = pathlib.join(location, 'package.xml');
|
||||
debug('Parsing %s', packageXmlPath);
|
||||
try {
|
||||
const packageXml = await (0, xml_1.readPackageXml)(packageXmlPath);
|
||||
const name = (0, xml_1.getNameFromPackageXml)(packageXml);
|
||||
const version = (0, xml_1.getVersionFromPackageXml)(packageXml);
|
||||
const path = (0, xml_1.getPathFromPackageXml)(packageXml);
|
||||
const apiLevel = (0, xml_1.getAPILevelFromPackageXml)(packageXml);
|
||||
pkg = {
|
||||
path,
|
||||
location,
|
||||
version,
|
||||
name,
|
||||
apiLevel,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
debug('Encountered error with %s: %O', packageXmlPath, e);
|
||||
if (e.code === 'ENOENT') {
|
||||
throw new errors_1.SDKException(`SDK package not found by location: ${location}.`, errors_1.ERR_SDK_PACKAGE_NOT_FOUND);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
pkgcache.set(location, pkg);
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
exports.getSDKPackage = getSDKPackage;
|
||||
async function resolveSDKRoot() {
|
||||
const debug = Debug(`${modulePrefix}:${resolveSDKRoot.name}`);
|
||||
debug('Looking for $ANDROID_HOME');
|
||||
// $ANDROID_HOME is deprecated, but still overrides $ANDROID_SDK_ROOT if
|
||||
// defined and valid.
|
||||
if (process.env.ANDROID_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_HOME))) {
|
||||
debug('Using $ANDROID_HOME at %s', process.env.ANDROID_HOME);
|
||||
return process.env.ANDROID_HOME;
|
||||
}
|
||||
debug('Looking for $ANDROID_SDK_ROOT');
|
||||
// No valid $ANDROID_HOME, try $ANDROID_SDK_ROOT.
|
||||
if (process.env.ANDROID_SDK_ROOT && (await (0, fs_1.isDir)(process.env.ANDROID_SDK_ROOT))) {
|
||||
debug('Using $ANDROID_SDK_ROOT at %s', process.env.ANDROID_SDK_ROOT);
|
||||
return process.env.ANDROID_SDK_ROOT;
|
||||
}
|
||||
const sdkDirs = exports.SDK_DIRECTORIES.get(process.platform);
|
||||
if (!sdkDirs) {
|
||||
throw new errors_1.SDKException(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
debug('Looking at following directories: %O', sdkDirs);
|
||||
for (const sdkDir of sdkDirs) {
|
||||
if (await (0, fs_1.isDir)(sdkDir)) {
|
||||
debug('Using %s', sdkDir);
|
||||
return sdkDir;
|
||||
}
|
||||
}
|
||||
throw new errors_1.SDKException(`No valid Android SDK root found.`, errors_1.ERR_SDK_NOT_FOUND);
|
||||
}
|
||||
exports.resolveSDKRoot = resolveSDKRoot;
|
||||
async function resolveEmulatorHome() {
|
||||
const debug = Debug(`${modulePrefix}:${resolveEmulatorHome.name}`);
|
||||
debug('Looking for $ANDROID_EMULATOR_HOME');
|
||||
if (process.env.ANDROID_EMULATOR_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_EMULATOR_HOME))) {
|
||||
debug('Using $ANDROID_EMULATOR_HOME at %s', process.env.ANDROID_EMULATOR_HOME);
|
||||
return process.env.ANDROID_EMULATOR_HOME;
|
||||
}
|
||||
debug('Looking at $HOME/.android');
|
||||
const homeEmulatorHome = pathlib.join(homedir, '.android');
|
||||
if (await (0, fs_1.isDir)(homeEmulatorHome)) {
|
||||
debug('Using $HOME/.android/ at %s', homeEmulatorHome);
|
||||
return homeEmulatorHome;
|
||||
}
|
||||
throw new errors_1.SDKException(`No valid Android Emulator home found.`, errors_1.ERR_EMULATOR_HOME_NOT_FOUND);
|
||||
}
|
||||
exports.resolveEmulatorHome = resolveEmulatorHome;
|
||||
async function resolveAVDHome() {
|
||||
const debug = Debug(`${modulePrefix}:${resolveAVDHome.name}`);
|
||||
debug('Looking for $ANDROID_AVD_HOME');
|
||||
if (process.env.ANDROID_AVD_HOME && (await (0, fs_1.isDir)(process.env.ANDROID_AVD_HOME))) {
|
||||
debug('Using $ANDROID_AVD_HOME at %s', process.env.ANDROID_AVD_HOME);
|
||||
return process.env.ANDROID_AVD_HOME;
|
||||
}
|
||||
debug('Looking at $HOME/.android/avd');
|
||||
const homeAvdHome = pathlib.join(homedir, '.android', 'avd');
|
||||
if (!(await (0, fs_1.isDir)(homeAvdHome))) {
|
||||
debug('Creating directory: %s', homeAvdHome);
|
||||
await (0, utils_fs_1.mkdirp)(homeAvdHome);
|
||||
}
|
||||
debug('Using $HOME/.android/avd/ at %s', homeAvdHome);
|
||||
return homeAvdHome;
|
||||
}
|
||||
exports.resolveAVDHome = resolveAVDHome;
|
||||
function supplementProcessEnv(sdk) {
|
||||
return {
|
||||
...process.env,
|
||||
ANDROID_SDK_ROOT: sdk.root,
|
||||
ANDROID_EMULATOR_HOME: sdk.emulatorHome,
|
||||
ANDROID_AVD_HOME: sdk.avdHome,
|
||||
};
|
||||
}
|
||||
exports.supplementProcessEnv = supplementProcessEnv;
|
||||
58
node_modules/native-run/dist/android/utils/sdk/xml.js
generated
vendored
Normal file
58
node_modules/native-run/dist/android/utils/sdk/xml.js
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getVersionFromPackageXml = exports.getNameFromPackageXml = exports.getPathFromPackageXml = exports.readPackageXml = exports.getAPILevelFromPackageXml = void 0;
|
||||
const utils_fs_1 = require("@ionic/utils-fs");
|
||||
const errors_1 = require("../../../errors");
|
||||
function getAPILevelFromPackageXml(packageXml) {
|
||||
var _a;
|
||||
const apiLevel = packageXml.find('./localPackage/type-details/api-level');
|
||||
return (_a = apiLevel === null || apiLevel === void 0 ? void 0 : apiLevel.text) === null || _a === void 0 ? void 0 : _a.toString();
|
||||
}
|
||||
exports.getAPILevelFromPackageXml = getAPILevelFromPackageXml;
|
||||
async function readPackageXml(path) {
|
||||
const et = await Promise.resolve().then(() => require('elementtree'));
|
||||
const contents = await (0, utils_fs_1.readFile)(path, { encoding: 'utf8' });
|
||||
const etree = et.parse(contents);
|
||||
return etree;
|
||||
}
|
||||
exports.readPackageXml = readPackageXml;
|
||||
function getPathFromPackageXml(packageXml) {
|
||||
const localPackage = packageXml.find('./localPackage');
|
||||
if (!localPackage) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
const path = localPackage.get('path');
|
||||
if (!path) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package path.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
return path.toString();
|
||||
}
|
||||
exports.getPathFromPackageXml = getPathFromPackageXml;
|
||||
function getNameFromPackageXml(packageXml) {
|
||||
const name = packageXml.find('./localPackage/display-name');
|
||||
if (!(name === null || name === void 0 ? void 0 : name.text)) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package name.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
return name.text.toString();
|
||||
}
|
||||
exports.getNameFromPackageXml = getNameFromPackageXml;
|
||||
function getVersionFromPackageXml(packageXml) {
|
||||
const versionElements = [
|
||||
packageXml.find('./localPackage/revision/major'),
|
||||
packageXml.find('./localPackage/revision/minor'),
|
||||
packageXml.find('./localPackage/revision/micro'),
|
||||
];
|
||||
const textFromElement = (e) => ((e === null || e === void 0 ? void 0 : e.text) ? e.text.toString() : '');
|
||||
const versions = [];
|
||||
for (const version of versionElements.map(textFromElement)) {
|
||||
if (!version) {
|
||||
break;
|
||||
}
|
||||
versions.push(version);
|
||||
}
|
||||
if (versions.length === 0) {
|
||||
throw new errors_1.SDKException(`Invalid SDK package version.`, errors_1.ERR_INVALID_SDK_PACKAGE);
|
||||
}
|
||||
return versions.join('.');
|
||||
}
|
||||
exports.getVersionFromPackageXml = getVersionFromPackageXml;
|
||||
Loading…
Add table
Add a link
Reference in a new issue