UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

368 lines • 17.6 kB
"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