<commit_before>///<reference path=".d.ts"/>
"use strict";

import * as path from "path";
import * as semver from "semver";

export class AndroidToolsInfo implements IAndroidToolsInfo {
	private static ANDROID_TARGET_PREFIX = "android";
	private static SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22"];
	private static MIN_REQUIRED_COMPILE_TARGET = 21;
	private static REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=22";
	private static VERSION_REGEX = /^(\d+\.){2}\d+$/;
	private showWarningsAsErrors: boolean;
	private toolsInfo: IAndroidToolsInfoData;
	private selectedCompileSdk: number;
	private installedTargetsCache: string[] = null;
	private androidHome = process.env["ANDROID_HOME"];

	constructor(private $childProcess: IChildProcess,
		private $errors: IErrors,
		private $fs: IFileSystem,
		private $logger: ILogger,
		private $options: IOptions) {}

	public getToolsInfo(): IFuture<IAndroidToolsInfoData> {
		return ((): IAndroidToolsInfoData => {
			if(!this.toolsInfo) {
				let infoData: IAndroidToolsInfoData = Object.create(null);
				infoData.androidHomeEnvVar = this.androidHome;
				infoData.compileSdkVersion = this.getCompileSdk().wait();
				infoData.buildToolsVersion = this.getBuildToolsVersion().wait();
				infoData.targetSdkVersion = this.getTargetSdk().wait();
				infoData.supportLibraryVersion = this.getAndroidSupportLibVersion().wait();

				this.toolsInfo = infoData;
			}

			return this.toolsInfo;
		}).future<IAndroidToolsInfoData>()();
	}

	public validateInfo(options?: {showWarningsAsErrors: boolean, validateTargetSdk: boolean}): IFuture<void> {
		return (() => {
			this.showWarningsAsErrors = options && options.showWarningsAsErrors;
			let toolsInfoData = this.getToolsInfo().wait();
			if(!toolsInfoData.androidHomeEnvVar || !this.$fs.exists(toolsInfoData.androidHomeEnvVar).wait()) {
				this.printMessage("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.",
					"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.");
			}

			if(!toolsInfoData.compileSdkVersion) {
				this.printMessage(`Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later.`,
							"Run `$ android` to manage your Android SDK versions.");
			}

			if(!toolsInfoData.buildToolsVersion) {
				this.printMessage(`You need to have the Android SDK Build-tools installed on your system. You can install any version in the following range: '${this.getBuildToolsRange()}'.`,
					'Run "android" from your command-line to install required Android Build Tools.');
			}

			if(!toolsInfoData.supportLibraryVersion) {
				this.printMessage(`You need to have the Android Support Library installed on your system. You can install any version in the following range: ${this.getAppCompatRange().wait() || ">=" + AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET}}.`,
					'Run `$ android`  to manage the Android Support Library.');
			}

			if(options && options.validateTargetSdk) {
				let targetSdk = toolsInfoData.targetSdkVersion;
				let newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`;
				if(!_.contains(AndroidToolsInfo.SUPPORTED_TARGETS, newTarget)) {
					let supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort();
					let minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions));

					if(targetSdk && (targetSdk < minSupportedVersion)) {
						this.printMessage(`The selected Android target SDK ${newTarget} is not supported. You пкяш target ${minSupportedVersion} or later.`);
					} else if(!targetSdk || targetSdk > this.getMaxSupportedVersion()) {
						this.$logger.warn(`Support for the selected Android target SDK ${newTarget} is not verified. Your Android app might not work as expected.`);
					}
				}
			}
		}).future<void>()();
	}

	/**
	 * Prints messages on the screen. In case the showWarningsAsErrors flag is set to true, warnings are shown, else - errors.
	 * Uses logger.warn for warnings and errors.failWithoutHelp when erros must be shown.
	 * In case additional details must be shown as info message, use the second parameter.
	 * NOTE: The additional information will not be printed when showWarningsAsErrors flag is set.
	 * @param  {string} msg The message that will be shown as warning or error.
	 * @param  {string} additionalMsg The additional message that will be shown as info message.
	 * @return {void}
	 */
	private printMessage(msg: string, additionalMsg?: string): void {
		if (this.showWarningsAsErrors) {
			this.$errors.failWithoutHelp(msg);
		} else {
			this.$logger.warn(msg);
		}

		if(additionalMsg) {
			this.$logger.info(additionalMsg);
		}
	}

	private getCompileSdk(): IFuture<number> {
		return ((): number => {
			if(!this.selectedCompileSdk) {
				let latestValidAndroidTarget = this.getLatestValidAndroidTarget().wait();
				if(latestValidAndroidTarget) {
					let integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget);

					if(integerVersion && integerVersion >= AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET) {
						this.selectedCompileSdk = integerVersion;
					}
				}
			}

			return this.selectedCompileSdk;
		}).future<number>()();
	}

	private getTargetSdk(): IFuture<number> {
		return ((): number => {
			let targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : this.getCompileSdk().wait();
			this.$logger.trace(`Selected targetSdk is: ${targetSdk}`);
			return targetSdk;
		}).future<number>()();
	}

	private getMatchingDir(pathToDir: string, versionRange: string): IFuture<string> {
		return ((): string => {
			let selectedVersion: string;
			if(this.$fs.exists(pathToDir).wait()) {
				let subDirs = this.$fs.readDirectory(pathToDir).wait()
					.filter(buildTools => !!buildTools.match(AndroidToolsInfo.VERSION_REGEX));
				this.$logger.trace(`Versions found in ${pathToDir} are ${subDirs.join(", ")}`);
				selectedVersion = semver.maxSatisfying(subDirs, versionRange);
			}

			return selectedVersion;
		}).future<string>()();
	}

	private getBuildToolsRange(): string {
		return `${AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX} <=${this.getMaxSupportedVersion()}`;
	}

	private getBuildToolsVersion(): IFuture<string> {
		return ((): string => {
			let pathToBuildTools = path.join(this.androidHome, "build-tools");
			let buildToolsRange = this.getBuildToolsRange();

			return this.getMatchingDir(pathToBuildTools, buildToolsRange).wait();
		}).future<string>()();
	}

	private getAppCompatRange(): IFuture<string> {
		return ((): string => {
			let compileSdkVersion = this.getCompileSdk().wait();
			let requiredAppCompatRange: string;
			if(compileSdkVersion) {
				requiredAppCompatRange = `>=${compileSdkVersion} <${compileSdkVersion + 1}`;
			}

			return requiredAppCompatRange;
		}).future<string>()();
	}

	private getAndroidSupportLibVersion(): IFuture<string> {
		return ((): string => {
			let pathToAppCompat = path.join(this.androidHome, "extras", "android", "m2repository", "com", "android", "support", "appcompat-v7");
			let requiredAppCompatRange = this.getAppCompatRange().wait();
			let selectedAppCompatVersion = requiredAppCompatRange ? this.getMatchingDir(pathToAppCompat, requiredAppCompatRange).wait() : undefined;
			this.$logger.trace(`Selected AppCompat version is: ${selectedAppCompatVersion}`);
			return selectedAppCompatVersion;
		}).future<string>()();
	}

	private getLatestValidAndroidTarget(): IFuture<string> {<commit_msg>Fix errors when ANDROID_HOME is not set<commit_after>///<reference path=".d.ts"/>
"use strict";

import * as path from "path";
import * as semver from "semver";

export class AndroidToolsInfo implements IAndroidToolsInfo {
	private static ANDROID_TARGET_PREFIX = "android";
	private static SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22"];
	private static MIN_REQUIRED_COMPILE_TARGET = 21;
	private static REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=22";
	private static VERSION_REGEX = /^(\d+\.){2}\d+$/;
	private showWarningsAsErrors: boolean;
	private toolsInfo: IAndroidToolsInfoData;
	private selectedCompileSdk: number;
	private installedTargetsCache: string[] = null;
	private androidHome = process.env["ANDROID_HOME"];

	constructor(private $childProcess: IChildProcess,
		private $errors: IErrors,
		private $fs: IFileSystem,
		private $logger: ILogger,
		private $options: IOptions) {}

	public getToolsInfo(): IFuture<IAndroidToolsInfoData> {
		return ((): IAndroidToolsInfoData => {
			if(!this.toolsInfo) {
				let infoData: IAndroidToolsInfoData = Object.create(null);
				infoData.androidHomeEnvVar = this.androidHome;
				infoData.compileSdkVersion = this.getCompileSdk().wait();
				infoData.buildToolsVersion = this.getBuildToolsVersion().wait();
				infoData.targetSdkVersion = this.getTargetSdk().wait();
				infoData.supportLibraryVersion = this.getAndroidSupportLibVersion().wait();

				this.toolsInfo = infoData;
			}

			return this.toolsInfo;
		}).future<IAndroidToolsInfoData>()();
	}

	public validateInfo(options?: {showWarningsAsErrors: boolean, validateTargetSdk: boolean}): IFuture<void> {
		return (() => {
			this.showWarningsAsErrors = options && options.showWarningsAsErrors;
			let toolsInfoData = this.getToolsInfo().wait();
			if(!toolsInfoData.androidHomeEnvVar || !this.$fs.exists(toolsInfoData.androidHomeEnvVar).wait()) {
				this.printMessage("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.",
					"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.");
			}

			if(!toolsInfoData.compileSdkVersion) {
				this.printMessage(`Cannot find a compatible Android SDK for compilation. To be able to build for Android, install Android SDK ${AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET} or later.`,
							"Run `$ android` to manage your Android SDK versions.");
			}

			if(!toolsInfoData.buildToolsVersion) {
				this.printMessage(`You need to have the Android SDK Build-tools installed on your system. You can install any version in the following range: '${this.getBuildToolsRange()}'.`,
					'Run "android" from your command-line to install required Android Build Tools.');
			}

			if(!toolsInfoData.supportLibraryVersion) {
				this.printMessage(`You need to have the Android Support Library installed on your system. You can install any version in the following range: ${this.getAppCompatRange().wait() || ">=" + AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET}}.`,
					'Run `$ android`  to manage the Android Support Library.');
			}

			if(options && options.validateTargetSdk) {
				let targetSdk = toolsInfoData.targetSdkVersion;
				let newTarget = `${AndroidToolsInfo.ANDROID_TARGET_PREFIX}-${targetSdk}`;
				if(!_.contains(AndroidToolsInfo.SUPPORTED_TARGETS, newTarget)) {
					let supportedVersions = AndroidToolsInfo.SUPPORTED_TARGETS.sort();
					let minSupportedVersion = this.parseAndroidSdkString(_.first(supportedVersions));

					if(targetSdk && (targetSdk < minSupportedVersion)) {
						this.printMessage(`The selected Android target SDK ${newTarget} is not supported. You пкяш target ${minSupportedVersion} or later.`);
					} else if(!targetSdk || targetSdk > this.getMaxSupportedVersion()) {
						this.$logger.warn(`Support for the selected Android target SDK ${newTarget} is not verified. Your Android app might not work as expected.`);
					}
				}
			}
		}).future<void>()();
	}

	/**
	 * Prints messages on the screen. In case the showWarningsAsErrors flag is set to true, warnings are shown, else - errors.
	 * Uses logger.warn for warnings and errors.failWithoutHelp when erros must be shown.
	 * In case additional details must be shown as info message, use the second parameter.
	 * NOTE: The additional information will not be printed when showWarningsAsErrors flag is set.
	 * @param  {string} msg The message that will be shown as warning or error.
	 * @param  {string} additionalMsg The additional message that will be shown as info message.
	 * @return {void}
	 */
	private printMessage(msg: string, additionalMsg?: string): void {
		if (this.showWarningsAsErrors) {
			this.$errors.failWithoutHelp(msg);
		} else {
			this.$logger.warn(msg);
		}

		if(additionalMsg) {
			this.$logger.info(additionalMsg);
		}
	}

	private getCompileSdk(): IFuture<number> {
		return ((): number => {
			if(!this.selectedCompileSdk) {
				let latestValidAndroidTarget = this.getLatestValidAndroidTarget().wait();
				if(latestValidAndroidTarget) {
					let integerVersion = this.parseAndroidSdkString(latestValidAndroidTarget);

					if(integerVersion && integerVersion >= AndroidToolsInfo.MIN_REQUIRED_COMPILE_TARGET) {
						this.selectedCompileSdk = integerVersion;
					}
				}
			}

			return this.selectedCompileSdk;
		}).future<number>()();
	}

	private getTargetSdk(): IFuture<number> {
		return ((): number => {
			let targetSdk = this.$options.sdk ? parseInt(this.$options.sdk) : this.getCompileSdk().wait();
			this.$logger.trace(`Selected targetSdk is: ${targetSdk}`);
			return targetSdk;
		}).future<number>()();
	}

	private getMatchingDir(pathToDir: string, versionRange: string): IFuture<string> {
		return ((): string => {
			let selectedVersion: string;
			if(this.$fs.exists(pathToDir).wait()) {
				let subDirs = this.$fs.readDirectory(pathToDir).wait()
					.filter(buildTools => !!buildTools.match(AndroidToolsInfo.VERSION_REGEX));
				this.$logger.trace(`Versions found in ${pathToDir} are ${subDirs.join(", ")}`);
				selectedVersion = semver.maxSatisfying(subDirs, versionRange);
			}

			return selectedVersion;
		}).future<string>()();
	}

	private getBuildToolsRange(): string {
		return `${AndroidToolsInfo.REQUIRED_BUILD_TOOLS_RANGE_PREFIX} <=${this.getMaxSupportedVersion()}`;
	}

	private getBuildToolsVersion(): IFuture<string> {
		return ((): string => {
			let buildToolsVersion: string;
			if(this.androidHome) {
				let pathToBuildTools = path.join(this.androidHome, "build-tools");
				let buildToolsRange = this.getBuildToolsRange();
				buildToolsVersion = this.getMatchingDir(pathToBuildTools, buildToolsRange).wait();
			}

			return buildToolsVersion;
		}).future<string>()();
	}

	private getAppCompatRange(): IFuture<string> {
		return ((): string => {
			let compileSdkVersion = this.getCompileSdk().wait();
			let requiredAppCompatRange: string;
			if(compileSdkVersion) {
				requiredAppCompatRange = `>=${compileSdkVersion} <${compileSdkVersion + 1}`;
			}

			return requiredAppCompatRange;
		}).future<string>()();
	}

	private getAndroidSupportLibVersion(): IFuture<string> {
		return ((): string => {
			let selectedAppCompatVersion: string;
			let requiredAppCompatRange = this.getAppCompatRange().wait();
			if(this.androidHome && requiredAppCompatRange) {
				let pathToAppCompat = path.join(this.androidHome, "extras", "android", "m2repository", "com", "android", "support", "appcompat-v7");
				selectedAppCompatVersion = this.getMatchingDir(pathToAppCompat, requiredAppCompatRange).wait();
			}

			this.$logger.trace(`Selected AppCompat version is: ${selectedAppCompatVersion}`);
			return selectedAppCompatVersion;
		}).future<string>()();
	}

	private getLatestValidAndroidTarget(): IFuture<string> {
{'fraction_non_alphanumeric': 0.12313624678663239, 'fraction_numerical': 0.002313624678663239, 'mean_word_length': 4.885400907715582, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4407826', 'n_tokens_mistral': 5051, 'n_tokens_neox': 4507, 'n_words': 1132}