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
JavaScript
"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";