204 lines
8.4 KiB
JavaScript
204 lines
8.4 KiB
JavaScript
"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;
|