UNPKG

nativescript-doctor

Version:

Library that helps identifying if the environment can be used for development of {N} apps.

353 lines (352 loc) 18.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const constants_1 = require("./constants"); const os_1 = require("os"); const semver = require("semver"); const path = require("path"); const _ = require("lodash"); class AndroidToolsInfo { constructor(childProcess, fs, hostInfo, helpers) { this.childProcess = childProcess; this.fs = fs; this.hostInfo = hostInfo; this.helpers = helpers; this.ANDROID_TARGET_PREFIX = "android"; } getSupportedTargets(projectDir) { const runtimeVersion = this.getRuntimeVersion({ projectDir }); let baseTargets = [ "android-17", "android-18", "android-19", "android-20", "android-21", "android-22", "android-23", "android-24", "android-25", "android-26", "android-27", "android-28", "android-29", "android-30" ]; if (runtimeVersion && semver.lt(semver.coerce(runtimeVersion), "6.1.0")) { baseTargets.sort(); const indexOfSdk29 = baseTargets.indexOf("android-29"); baseTargets = baseTargets.slice(0, indexOfSdk29); } return baseTargets; } get androidHome() { return process.env["ANDROID_HOME"]; } getToolsInfo(config = {}) { if (!this.toolsInfo) { const infoData = Object.create(null); infoData.androidHomeEnvVar = this.androidHome; infoData.installedTargets = this.getInstalledTargets(); infoData.compileSdkVersion = this.getCompileSdk(infoData.installedTargets, config.projectDir); infoData.buildToolsVersion = this.getBuildToolsVersion(config.projectDir); this.toolsInfo = infoData; } return this.toolsInfo; } validateInfo(config = {}) { const errors = []; const toolsInfoData = this.getToolsInfo(config); const isAndroidHomeValid = this.isAndroidHomeValid(); const runtimeVersion = this.getRuntimeVersion(config); const supportsOnlyMinRequiredCompileTarget = this.getMaxSupportedCompileVersion(runtimeVersion) === AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET; if (!toolsInfoData.compileSdkVersion) { errors.push({ warning: `Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET}${supportsOnlyMinRequiredCompileTarget ? "" : " or later"}.`, additionalInformation: `Run \`\$ ${this.getPathToSdkManagementTool()}\` to manage your Android SDK versions.`, platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } if (!toolsInfoData.buildToolsVersion) { const buildToolsRange = this.getBuildToolsRange(config.projectDir); const versionRangeMatches = buildToolsRange.match(/^.*?([\d\.]+)\s+.*?([\d\.]+)$/); let message = `You can install any version in the following range: '${buildToolsRange}'.`; if (versionRangeMatches && versionRangeMatches[1] && versionRangeMatches[2] && versionRangeMatches[1] === versionRangeMatches[2]) { message = `You have to install version ${versionRangeMatches[1]}.`; } let invalidBuildToolsAdditionalMsg = `Run \`\$ ${this.getPathToSdkManagementTool()}\` from your command-line to install required \`Android Build Tools\`.`; if (!isAndroidHomeValid) { invalidBuildToolsAdditionalMsg += ' In case you already have them installed, make sure `ANDROID_HOME` environment variable is set correctly.'; } errors.push({ warning: "You need to have the Android SDK Build-tools installed on your system. " + message, additionalInformation: invalidBuildToolsAdditionalMsg, platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } return errors; } validateJavacVersion(installedJavaCompilerVersion, projectDir, runtimeVersion) { const errors = []; let additionalMessage = "You will not be able to build your projects for Android." + os_1.EOL + "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + os_1.EOL + " described in " + this.getSystemRequirementsLink(); const matchingVersion = this.helpers.appendZeroesToVersion(installedJavaCompilerVersion || "", 3).match(AndroidToolsInfo.VERSION_REGEX); const installedJavaCompilerSemverVersion = matchingVersion && matchingVersion[1]; if (installedJavaCompilerSemverVersion) { let warning = null; const supportedVersions = { "^10.0.0": "4.1.0-2018.5.18.1" }; if (semver.lt(installedJavaCompilerSemverVersion, AndroidToolsInfo.MIN_JAVA_VERSION) || semver.gte(installedJavaCompilerSemverVersion, AndroidToolsInfo.MAX_JAVA_VERSION)) { warning = `Javac version ${installedJavaCompilerVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION} and below ${AndroidToolsInfo.MAX_JAVA_VERSION}.`; } else { runtimeVersion = this.getRuntimeVersion({ runtimeVersion, projectDir }); if (runtimeVersion) { let runtimeMinVersion = null; Object.keys(supportedVersions) .forEach(javacRange => { if (semver.satisfies(installedJavaCompilerSemverVersion, javacRange)) { runtimeMinVersion = supportedVersions[javacRange]; } }); if (runtimeMinVersion && semver.lt(runtimeVersion, runtimeMinVersion)) { warning = `The Java compiler version ${installedJavaCompilerVersion} is not compatible with the current Android runtime version ${runtimeVersion}. ` + `In order to use this Javac version, you need to update your Android runtime or downgrade your Java compiler version.`; additionalMessage = "You will not be able to build your projects for Android." + os_1.EOL + "To be able to build for Android, downgrade your Java compiler version or update your Android runtime."; } } } if (warning) { errors.push({ warning, additionalInformation: additionalMessage, platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } } else { errors.push({ warning: "Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.", additionalInformation: additionalMessage, platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } return errors; } getPathToAdbFromAndroidHome() { return __awaiter(this, void 0, void 0, function* () { if (this.androidHome) { const pathToAdb = path.join(this.androidHome, "platform-tools", "adb"); try { yield this.childProcess.execFile(pathToAdb, ["help"]); return pathToAdb; } catch (err) { return null; } } return null; }); } validateAndroidHomeEnvVariable() { const errors = []; const expectedDirectoriesInAndroidHome = ["build-tools", "tools", "platform-tools", "extras"]; if (!this.androidHome || !this.fs.exists(this.androidHome)) { errors.push({ warning: "The ANDROID_HOME environment variable is not set or it points to a non-existent directory. You will not be able to perform any build-related operations for Android.", additionalInformation: "To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory.", platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } else if (expectedDirectoriesInAndroidHome.map(dir => this.fs.exists(path.join(this.androidHome, dir))).length === 0) { errors.push({ warning: "The ANDROID_HOME environment variable points to incorrect directory. You will not be able to perform any build-related operations for Android.", additionalInformation: "To be able to perform Android build-related operations, set the `ANDROID_HOME` variable to point to the root of your Android SDK installation directory, " + "where you will find `tools` and `platform-tools` directories.", platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } return errors; } validateMinSupportedTargetSdk({ targetSdk, projectDir }) { const errors = []; const newTarget = `${this.ANDROID_TARGET_PREFIX}-${targetSdk}`; const supportedTargets = this.getSupportedTargets(projectDir); const targetSupported = _.includes(supportedTargets, newTarget); if (!_.includes(supportedTargets, newTarget)) { const supportedVersions = supportedTargets.sort(); const minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions)); if (!targetSupported && targetSdk && (targetSdk < minSupportedVersion)) { errors.push({ warning: `The selected Android target SDK ${newTarget} is not supported. You must target ${minSupportedVersion} or later.`, additionalInformation: "", platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } } return errors; } validataMaxSupportedTargetSdk({ targetSdk, projectDir }) { const errors = []; const newTarget = `${this.ANDROID_TARGET_PREFIX}-${targetSdk}`; const targetSupported = _.includes(this.getSupportedTargets(projectDir), newTarget); if (!targetSupported && !targetSdk || targetSdk > this.getMaxSupportedVersion(projectDir)) { errors.push({ warning: `Support for the selected Android target SDK ${newTarget} is not verified. Your Android app might not work as expected.`, additionalInformation: "", platforms: [constants_1.Constants.ANDROID_PLATFORM_NAME] }); } return errors; } getPathToEmulatorExecutable() { if (!this.pathToEmulatorExecutable) { const emulatorExecutableName = "emulator"; this.pathToEmulatorExecutable = emulatorExecutableName; if (this.androidHome) { const pathToEmulatorFromAndroidStudio = path.join(this.androidHome, emulatorExecutableName, emulatorExecutableName); const realFilePath = this.hostInfo.isWindows ? `${pathToEmulatorFromAndroidStudio}.exe` : pathToEmulatorFromAndroidStudio; if (this.fs.exists(realFilePath)) { this.pathToEmulatorExecutable = pathToEmulatorFromAndroidStudio; } else { this.pathToEmulatorExecutable = path.join(this.androidHome, "tools", emulatorExecutableName); } } } return this.pathToEmulatorExecutable; } getPathToSdkManagementTool() { const sdkmanagerName = "sdkmanager"; let sdkManagementToolPath = sdkmanagerName; const isAndroidHomeValid = this.isAndroidHomeValid(); if (isAndroidHomeValid) { const pathToSdkmanager = path.join(this.androidHome, "tools", "bin", sdkmanagerName); const pathToAndroidExecutable = path.join(this.androidHome, "tools", "android"); const pathToExecutable = this.fs.exists(pathToSdkmanager) ? pathToSdkmanager : pathToAndroidExecutable; sdkManagementToolPath = pathToExecutable.replace(this.androidHome, this.hostInfo.isWindows ? "%ANDROID_HOME%" : "$ANDROID_HOME"); } return sdkManagementToolPath; } getCompileSdk(installedTargets, projectDir) { const latestValidAndroidTarget = this.getLatestValidAndroidTarget(installedTargets, projectDir); if (latestValidAndroidTarget) { const integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget); if (integerVersion && integerVersion >= AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET) { return integerVersion; } } } getMatchingDir(pathToDir, versionRange) { let selectedVersion; if (this.fs.exists(pathToDir)) { const subDirs = this.fs.readDirectory(pathToDir); const subDirsVersions = subDirs .map(dirName => { const dirNameGroups = dirName.match(AndroidToolsInfo.VERSION_REGEX); if (dirNameGroups) { return dirNameGroups[1]; } return null; }) .filter(dirName => !!dirName); const version = semver.maxSatisfying(subDirsVersions, versionRange); if (version) { selectedVersion = subDirs.find(dir => dir.indexOf(version) !== -1); } } return selectedVersion; } getBuildToolsRange(projectDir) { return `${AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX} <=${this.getMaxSupportedVersion(projectDir)}`; } getBuildToolsVersion(projectDir) { let buildToolsVersion; if (this.androidHome) { const pathToBuildTools = path.join(this.androidHome, "build-tools"); const buildToolsRange = this.getBuildToolsRange(projectDir); buildToolsVersion = this.getMatchingDir(pathToBuildTools, buildToolsRange); } return buildToolsVersion; } getLatestValidAndroidTarget(installedTargets, projectDir) { return _.findLast(this.getSupportedTargets(projectDir).sort(), supportedTarget => _.includes(installedTargets, supportedTarget)); } parseAndroidSdkString(androidSdkString) { return parseInt(androidSdkString.replace(`${this.ANDROID_TARGET_PREFIX}-`, "")); } getInstalledTargets() { try { const pathToInstalledTargets = path.join(this.androidHome, "platforms"); if (!this.fs.exists(pathToInstalledTargets)) { throw new Error("No Android Targets installed."); } return this.fs.readDirectory(pathToInstalledTargets); } catch (err) { return []; } } getMaxSupportedVersion(projectDir) { const supportedTargets = this.getSupportedTargets(projectDir); return this.parseAndroidSdkString(supportedTargets.sort()[supportedTargets.length - 1]); } getSystemRequirementsLink() { return constants_1.Constants.SYSTEM_REQUIREMENTS_LINKS[process.platform] || ""; } isAndroidHomeValid() { const errors = this.validateAndroidHomeEnvVariable(); return !errors && !errors.length; } getAndroidRuntimeVersionFromProjectDir(projectDir) { let runtimeVersion = null; if (projectDir && this.fs.exists(projectDir)) { const pathToPackageJson = path.join(projectDir, constants_1.Constants.PACKAGE_JSON); if (this.fs.exists(pathToPackageJson)) { const content = this.fs.readJson(pathToPackageJson); runtimeVersion = content && content.nativescript && content.nativescript["tns-android"] && content.nativescript["tns-android"].version; } } return runtimeVersion; } getRuntimeVersion({ runtimeVersion, projectDir }) { runtimeVersion = runtimeVersion || this.getAndroidRuntimeVersionFromProjectDir(projectDir); if (runtimeVersion) { if (!semver.valid(runtimeVersion)) { try { const npmViewOutput = this.childProcess.execSync(`npm view ${constants_1.Constants.ANDROID_RUNTIME} dist-tags --json`); const jsonNpmViewOutput = JSON.parse(npmViewOutput); runtimeVersion = jsonNpmViewOutput[runtimeVersion] || runtimeVersion; } catch (err) { } } } if (runtimeVersion && !semver.valid(runtimeVersion)) { throw new Error(`The determined Android runtime version ${runtimeVersion} is not valid. Unable to verify if the current system is setup for Android development.`); } return runtimeVersion; } getMaxSupportedCompileVersion(runtimeVersion) { if (runtimeVersion && semver.lt(semver.coerce(runtimeVersion), "6.1.0")) { return 28; } return this.parseAndroidSdkString(_.last(this.getSupportedTargets(runtimeVersion).sort())); } } exports.AndroidToolsInfo = AndroidToolsInfo; AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET = 28; AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=23"; AndroidToolsInfo.VERSION_REGEX = /((\d+\.){2}\d+)/; AndroidToolsInfo.MIN_JAVA_VERSION = "1.8.0"; AndroidToolsInfo.MAX_JAVA_VERSION = "13.0.0";