nativescript
Version:
Command-line interface for building NativeScript projects
368 lines • 17.6 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AndroidVirtualDeviceService = void 0;
const net = require("net");
const path = require("path");
const os_1 = require("os");
const _ = require("lodash");
const os_2 = require("os");
const constants_1 = require("../../constants");
const decorators_1 = require("../../decorators");
const helpers_1 = require("../../helpers");
const constants_2 = require("../../../constants");
const yok_1 = require("../../yok");
class AndroidVirtualDeviceService {
constructor($androidIniFileParser, $childProcess, $devicePlatformsConstants, $emulatorHelper, $fs, $hostInfo, $sysInfo, $logger) {
this.$androidIniFileParser = $androidIniFileParser;
this.$childProcess = $childProcess;
this.$devicePlatformsConstants = $devicePlatformsConstants;
this.$emulatorHelper = $emulatorHelper;
this.$fs = $fs;
this.$hostInfo = $hostInfo;
this.$sysInfo = $sysInfo;
this.$logger = $logger;
this.mapEmulatorIdToImageIdentifier = {};
this.androidHome = process.env.ANDROID_HOME;
}
async getEmulatorImages(adbDevicesOutput) {
const availableEmulatorsOutput = await this.getEmulatorImagesCore();
const avds = availableEmulatorsOutput.devices;
const runningEmulatorIds = await this.getRunningEmulatorIds(adbDevicesOutput);
const runningEmulators = await (0, helpers_1.settlePromises)(_.map(runningEmulatorIds, (emulatorId) => this.getRunningEmulatorData(emulatorId, avds)));
const devices = availableEmulatorsOutput.devices.map((emulator) => this.$emulatorHelper.getEmulatorByImageIdentifier(emulator.imageIdentifier, runningEmulators) || emulator);
return {
devices,
errors: availableEmulatorsOutput.errors,
};
}
async getRunningEmulatorIds(adbDevicesOutput) {
const emulatorIds = _.reduce(adbDevicesOutput, (result, device) => {
const rx = device.match(constants_1.AndroidVirtualDevice.RUNNING_AVD_EMULATOR_REGEX);
if (rx && rx[1]) {
result.push(rx[1]);
}
return result;
}, []);
return emulatorIds;
}
async getRunningEmulatorName(emulatorId) {
const imageIdentifier = await this.getRunningEmulatorImageIdentifier(emulatorId);
const iniFilePath = path.join(this.pathToAvdHomeDir, `${imageIdentifier}.ini`);
const iniFileInfo = this.$androidIniFileParser.parseIniFile(iniFilePath);
let result = imageIdentifier;
if (iniFileInfo && iniFileInfo.path) {
const configIniFileInfo = this.$androidIniFileParser.parseIniFile(path.join(iniFileInfo.path, constants_1.AndroidVirtualDevice.CONFIG_INI_FILE_NAME));
result =
(configIniFileInfo && configIniFileInfo.displayName) || imageIdentifier;
}
return result;
}
startEmulatorArgs(imageIdentifier) {
return ["-avd", imageIdentifier];
}
get pathToEmulatorExecutable() {
const emulatorExecutableName = "emulator";
if (this.androidHome) {
// Check https://developer.android.com/studio/releases/sdk-tools.html (25.3.0)
// Since this version of SDK tools, the emulator is a separate package.
// However the emulator executable still exists in the "tools" dir.
const pathToEmulatorFromAndroidStudio = path.join(this.androidHome, emulatorExecutableName, emulatorExecutableName);
const realFilePath = this.$hostInfo.isWindows
? `${pathToEmulatorFromAndroidStudio}.exe`
: pathToEmulatorFromAndroidStudio;
if (this.$fs.exists(realFilePath)) {
return pathToEmulatorFromAndroidStudio;
}
return path.join(this.androidHome, "tools", emulatorExecutableName);
}
return emulatorExecutableName;
}
getRunningEmulatorImageIdentifier(emulatorId) {
if (this.mapEmulatorIdToImageIdentifier[emulatorId]) {
return Promise.resolve(this.mapEmulatorIdToImageIdentifier[emulatorId]);
}
const match = emulatorId.match(/^emulator-(\d+)/);
const portNumber = match && match[1];
if (!portNumber) {
return Promise.resolve(null);
}
return new Promise((resolveBase) => {
let isResolved = false;
let output = "";
const resolve = (result) => {
if (!isResolved) {
isResolved = true;
resolveBase(result);
}
};
const client = net.connect(portNumber, () => {
client.write(`avd name${os_1.EOL}`);
});
const timer = setTimeout(() => {
this.clearNetConnection(client, timer);
resolve(null);
}, 5000);
client.on("data", (data) => {
output += data.toString();
const imageIdentifier = this.getImageIdentifierFromClientOutput(output);
// old output should look like:
// Android Console: type 'help' for a list of commands
// OK
// <Name of image>
// OK
// new output should look like:
// Android Console: type 'help' for a list of commands
// OK
// a\u001b[K\u001b[Dav\u001b[K\u001b[D\u001b[Davd\u001b...
// <Name of image>
// OK
if (imageIdentifier && !isResolved) {
this.mapEmulatorIdToImageIdentifier[emulatorId] = imageIdentifier;
this.clearNetConnection(client, timer);
resolve(imageIdentifier);
}
});
client.on("error", (error) => {
this.$logger.trace(`Error while checking emulator identifier for ${emulatorId}. More info: ${error}.`);
resolve(null);
});
});
}
detach(deviceInfo) {
if (this.mapEmulatorIdToImageIdentifier[deviceInfo.identifier]) {
delete this.mapEmulatorIdToImageIdentifier[deviceInfo.identifier];
}
}
async getEmulatorImagesCore() {
let result = null;
let devices = [];
let errors = [];
const canExecuteAvdManagerCommand = await this.canExecuteAvdManagerCommand();
if (!canExecuteAvdManagerCommand) {
errors = [
"Unable to execute avdmanager, ensure JAVA_HOME is set and points to correct directory",
];
}
if (canExecuteAvdManagerCommand) {
const sanitizedPathToAvdManagerExecutable = this.$hostInfo.isWindows
? (0, helpers_1.quoteString)(this.pathToAvdManagerExecutable)
: this.pathToAvdManagerExecutable;
result = await this.$childProcess.trySpawnFromCloseEvent(sanitizedPathToAvdManagerExecutable, ["list", "avds"], { shell: this.$hostInfo.isWindows });
}
else if (this.pathToAndroidExecutable &&
this.$fs.exists(this.pathToAndroidExecutable)) {
result = await this.$childProcess.trySpawnFromCloseEvent(this.pathToAndroidExecutable, ["list", "avd"]);
}
if (result && result.stdout) {
devices = this.parseListAvdsOutput(result.stdout);
errors = result && result.stderr ? [result.stderr] : [];
}
else {
devices = this.listAvdsFromDirectory();
}
return { devices, errors };
}
async canExecuteAvdManagerCommand() {
let canExecute = false;
if (this.pathToAvdManagerExecutable &&
this.$fs.exists(this.pathToAvdManagerExecutable)) {
if (process.env.JAVA_HOME) {
// In case JAVA_HOME is set, but it points to incorrect directory (i.e. there's no java in $JAVA_HOME/bin/java), avdmanager will fail
// no matter if you have correct java in PATH.
canExecute = !!(await this.$sysInfo.getJavaVersionFromJavaHome());
}
else {
canExecute = !!(await this.$sysInfo.getJavaVersionFromPath());
}
}
return canExecute;
}
async getRunningEmulatorData(runningEmulatorId, availableEmulators) {
const imageIdentifier = await this.getRunningEmulatorImageIdentifier(runningEmulatorId);
const runningEmulator = this.$emulatorHelper.getEmulatorByImageIdentifier(imageIdentifier, availableEmulators);
if (!runningEmulator) {
return null;
}
this.$emulatorHelper.setRunningAndroidEmulatorProperties(runningEmulatorId, runningEmulator);
return runningEmulator;
}
get pathToAvdManagerExecutable() {
let avdManagerPath = null;
if (this.androidHome) {
avdManagerPath = path.join(this.androidHome, "tools", "bin", this.getExecutableName("avdmanager"));
}
return avdManagerPath;
}
get pathToAndroidExecutable() {
let androidPath = null;
if (this.androidHome) {
androidPath = path.join(this.androidHome, "tools", this.getExecutableName("android"));
}
return androidPath;
}
get pathToAvdHomeDir() {
const searchPaths = [
process.env.ANDROID_AVD_HOME,
path.join((0, os_2.homedir)(), constants_1.AndroidVirtualDevice.ANDROID_DIR_NAME, constants_1.AndroidVirtualDevice.AVD_DIR_NAME),
];
return searchPaths.find((p) => p && this.$fs.exists(p));
}
getConfigurationError() {
const pathToEmulatorExecutable = this.$hostInfo.isWindows
? `${this.pathToEmulatorExecutable}.exe`
: this.pathToEmulatorExecutable;
if (!this.$fs.exists(pathToEmulatorExecutable)) {
return "Unable to find the path to emulator executable and will not be able to start the emulator. Searched paths: [$ANDROID_HOME/tools/emulator, $ANDROID_HOME/emulator/emulator]";
}
return null;
}
getExecutableName(executable) {
if (this.$hostInfo.isWindows) {
return `${executable}.bat`;
}
return executable;
}
listAvdsFromDirectory() {
let devices = [];
if (this.pathToAvdHomeDir && this.$fs.exists(this.pathToAvdHomeDir)) {
const entries = this.$fs.readDirectory(this.pathToAvdHomeDir);
devices = _.filter(entries, (e) => e.match(constants_1.AndroidVirtualDevice.AVD_FILES_MASK) !== null)
.map((e) => e.match(constants_1.AndroidVirtualDevice.AVD_FILES_MASK)[1])
.map((avdName) => path.join(this.pathToAvdHomeDir, `${avdName}.avd`))
.map((avdPath) => this.getInfoFromAvd(avdPath))
.filter((avdInfo) => !!avdInfo)
.map((avdInfo) => this.convertAvdToDeviceInfo(avdInfo));
}
return devices;
}
parseListAvdsOutput(output) {
let devices = [];
const avdOutput = output.split(constants_1.AndroidVirtualDevice.AVAILABLE_AVDS_MESSAGE);
const availableDevices = avdOutput && avdOutput[1] && avdOutput[1].trim();
if (availableDevices) {
// In some cases `avdmanager list avds` command prints:
// `The following Android Virtual Devices could not be loaded:
// Name: Pixel_2_XL_API_28
// Path: /Users/<username>/.android/avd/Pixel_2_XL_API_28.avd
// Error: Google pixel_2_xl no longer exists as a device`
// These devices sometimes are valid so try to parse them.
// Also these devices are printed at the end of the output and are separated with 2 new lines from the valid devices output.
const parts = availableDevices.split(/(?:\r?\n){2}/);
const items = [parts[0], parts[1]].filter((item) => !!item);
for (const item of items) {
const result = item
.split(constants_1.AndroidVirtualDevice.AVD_LIST_DELIMITER)
.map((singleDeviceOutput) => this.getAvdManagerDeviceInfo(singleDeviceOutput.trim()))
.map((avdManagerDeviceInfo) => this.getInfoFromAvd(avdManagerDeviceInfo.path))
.filter((avdInfo) => !!avdInfo)
.map((avdInfo) => this.convertAvdToDeviceInfo(avdInfo));
devices = devices.concat(result);
}
}
return devices;
}
getAvdManagerDeviceInfo(output) {
const avdManagerDeviceInfo = Object.create(null);
// Split by `\n`, not EOL as the avdmanager and android executables print results with `\n` only even on Windows
_.reduce(output.split("\n"), (result, row) => {
const [key, value] = row.split(": ").map((part) => part.trim());
switch (key) {
case "Name":
case "Device":
case "Path":
case "Target":
case "Skin":
case "Sdcard":
result[key.toLowerCase()] = value;
break;
}
return result;
}, avdManagerDeviceInfo || {});
return avdManagerDeviceInfo;
}
getInfoFromAvd(avdFilePath) {
const configIniFilePath = path.join(avdFilePath, constants_1.AndroidVirtualDevice.CONFIG_INI_FILE_NAME);
const configIniFileInfo = this.$androidIniFileParser.parseIniFile(configIniFilePath);
const iniFilePath = this.getIniFilePath(configIniFileInfo, avdFilePath);
const iniFileInfo = this.$androidIniFileParser.parseIniFile(iniFilePath);
_.extend(configIniFileInfo, iniFileInfo);
if (configIniFileInfo && !configIniFileInfo.avdId) {
configIniFileInfo.avdId = path
.basename(avdFilePath)
.replace(constants_1.AndroidVirtualDevice.AVD_FILE_EXTENSION, "");
}
return configIniFileInfo;
}
convertAvdToDeviceInfo(avdInfo) {
return {
identifier: null,
imageIdentifier: avdInfo.avdId || avdInfo.displayName,
displayName: avdInfo.displayName || avdInfo.avdId || avdInfo.device,
model: avdInfo.device,
version: this.$emulatorHelper.mapAndroidApiLevelToVersion[avdInfo.target],
vendor: constants_1.AndroidVirtualDevice.AVD_VENDOR_NAME,
status: constants_1.NOT_RUNNING_EMULATOR_STATUS,
errorHelp: this.getConfigurationError(),
isTablet: false,
type: constants_1.DeviceTypes.Emulator,
connectionTypes: [constants_2.DeviceConnectionType.Local],
platform: this.$devicePlatformsConstants.Android,
};
}
getImageIdentifierFromClientOutput(output) {
// The lines should be trimmed after the split because the output has \r\n and when using split(EOL) on mac each line ends with \r.
const lines = _.map(output.split(os_1.EOL), (line) => line.trim());
const firstIndexOfOk = _.indexOf(lines, "OK");
if (firstIndexOfOk < 0) {
return null;
}
const secondIndexOfOk = _.indexOf(lines, "OK", firstIndexOfOk + 1);
if (secondIndexOfOk < 0) {
return null;
}
return lines[secondIndexOfOk - 1].trim();
}
getIniFilePath(configIniFileInfo, avdFilePath) {
let result = avdFilePath.replace(constants_1.AndroidVirtualDevice.AVD_FILE_EXTENSION, constants_1.AndroidVirtualDevice.INI_FILE_EXTENSION);
if (configIniFileInfo && configIniFileInfo.avdId) {
result = path.join(path.dirname(avdFilePath), `${configIniFileInfo.avdId}${constants_1.AndroidVirtualDevice.INI_FILE_EXTENSION}`);
}
return result;
}
clearNetConnection(client, timer) {
if (client) {
client.removeAllListeners();
client.destroy();
}
if (timer) {
clearTimeout(timer);
}
}
}
exports.AndroidVirtualDeviceService = AndroidVirtualDeviceService;
__decorate([
(0, decorators_1.cache)()
], AndroidVirtualDeviceService.prototype, "pathToEmulatorExecutable", null);
__decorate([
(0, decorators_1.cache)()
], AndroidVirtualDeviceService.prototype, "canExecuteAvdManagerCommand", null);
__decorate([
(0, decorators_1.cache)()
], AndroidVirtualDeviceService.prototype, "pathToAvdManagerExecutable", null);
__decorate([
(0, decorators_1.cache)()
], AndroidVirtualDeviceService.prototype, "pathToAndroidExecutable", null);
__decorate([
(0, decorators_1.cache)()
], AndroidVirtualDeviceService.prototype, "pathToAvdHomeDir", null);
__decorate([
(0, decorators_1.cache)()
], AndroidVirtualDeviceService.prototype, "getConfigurationError", null);
yok_1.injector.register("androidVirtualDeviceService", AndroidVirtualDeviceService);
//# sourceMappingURL=android-virtual-device-service.js.map