From 254bfc51d7e63180322bf89f7eba523191fb5773 Mon Sep 17 00:00:00 2001 From: Priyansh Garg Date: Tue, 11 Oct 2022 20:31:38 +0530 Subject: [PATCH] Add `ANDROID_HOME` flow. --- package-lock.json | 28 +++++++ package.json | 2 + src/commands/android/index.ts | 118 +++++++++++++++++++++++------ src/commands/android/interfaces.ts | 4 + src/commands/android/utils.ts | 18 +++++ 5 files changed, 147 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15d6c09..568b2b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,11 @@ "dependencies": { "ansi-colors": "^4.1.3", "cli-progress": "^3.11.2", + "dotenv": "^16.0.3", "download": "^8.0.0", "inquirer": "^8.2.4", "minimist": "^1.2.6", + "untildify": "^4.0.0", "which": "^2.0.2" }, "bin": { @@ -1048,6 +1050,14 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/download": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/download/-/download-8.0.0.tgz", @@ -2976,6 +2986,14 @@ "through": "^2.3.8" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "engines": { + "node": ">=8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3828,6 +3846,11 @@ "esutils": "^2.0.2" } }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, "download": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/download/-/download-8.0.0.tgz", @@ -5258,6 +5281,11 @@ "through": "^2.3.8" } }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==" + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 1d7849e..7151712 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,11 @@ "dependencies": { "ansi-colors": "^4.1.3", "cli-progress": "^3.11.2", + "dotenv": "^16.0.3", "download": "^8.0.0", "inquirer": "^8.2.4", "minimist": "^1.2.6", + "untildify": "^4.0.0", "which": "^2.0.2" } } diff --git a/src/commands/android/index.ts b/src/commands/android/index.ts index f832955..7d6f45c 100644 --- a/src/commands/android/index.ts +++ b/src/commands/android/index.ts @@ -1,42 +1,51 @@ import colors from 'ansi-colors'; +import * as dotenv from 'dotenv'; import fs from 'fs'; import path from 'path'; +import untildify from 'untildify'; import which from 'which'; import {execSync} from 'child_process'; import {prompt} from 'inquirer'; import {getPlatformName, symbols} from '../../utils'; import {BINARY_TO_PACKAGE_NAME, NIGHTWATCH_AVD, SDK_BINARY_LOCATIONS, SETUP_CONFIG_QUES} from './constants'; -import {Options, Platform, SdkBinary, SetupConfigs} from './interfaces'; -import {downloadAndSetupAndroidSdk, getAbiForOS, getBinaryNameForOS, installPackagesUsingSdkManager} from './utils'; +import {Options, OtherInfo, Platform, SdkBinary, SetupConfigs} from './interfaces'; +import { + downloadAndSetupAndroidSdk, getAbiForOS, getBinaryNameForOS, + getDefaultAndroidSdkRoot, installPackagesUsingSdkManager +} from './utils'; export class AndroidSetup { sdkRoot: string; options: Options; - cwd: string; + rootDir: string; platform: Platform; binaryLocation: {[key: string]: string}; + otherInfo: OtherInfo; - constructor(options: Options, cwd = process.cwd()) { + constructor(options: Options, rootDir = process.cwd()) { this.sdkRoot = ''; this.options = options; - this.cwd = cwd; + this.rootDir = rootDir; this.platform = getPlatformName(); this.binaryLocation = {}; + this.otherInfo = { + androidHomeInGlobalEnv: false + }; } async run() { + let result = true; + if (this.options.help) { this.showHelp(); - return true; + return result; } - this.sdkRoot = this.getSdkRoot(); - if (this.sdkRoot === '') { - return false; - } + const sdkRootEnv = this.getSdkRootFromEnv(); + this.sdkRoot = sdkRootEnv || await this.getSdkRootFromUser(); const setupConfigs: SetupConfigs = await this.getSetupConfigs(this.options); console.log(); @@ -44,45 +53,85 @@ export class AndroidSetup { const missingRequirements = this.verifySetup(setupConfigs); if (this.options.setup) { - return await this.setupAndroid(setupConfigs, missingRequirements); + result = await this.setupAndroid(setupConfigs, missingRequirements); } else if (missingRequirements.length) { - return false; + result = false; } - return true; + if (!sdkRootEnv) { + this.sdkRootEnvSetInstructions(); + } + + return result; } showHelp() { console.log('Help menu for android'); } - getSdkRoot(): string { + getSdkRootFromEnv(): string { console.log('Checking the value of ANDROID_HOME environment variable...'); + this.otherInfo.androidHomeInGlobalEnv = 'ANDROID_HOME' in process.env; + + dotenv.config({path: path.join(this.rootDir, '.env')}); + const androidHome = process.env.ANDROID_HOME; + const fromDotEnv = this.otherInfo.androidHomeInGlobalEnv ? '' : ' (taken from .env)'; + if (androidHome) { - console.log( - ` ${colors.green(symbols().ok)} ANDROID_HOME is set to '${androidHome}'\n` - ); + const androidHomeFinal = untildify(androidHome); + + const androidHomeAbsolute = path.resolve(this.rootDir, androidHomeFinal); + if (androidHomeFinal !== androidHomeAbsolute) { + console.log(` ${colors.yellow('!')} ANDROID_HOME is set to '${androidHomeFinal}'${fromDotEnv} which is NOT an absolute path.`); + console.log(` ${colors.green(symbols().ok)} Considering ANDROID_HOME to be '${androidHomeAbsolute}'\n`); + + return androidHomeAbsolute; + } + + console.log(` ${colors.green(symbols().ok)} ANDROID_HOME is set to '${androidHomeFinal}'${fromDotEnv}\n`); - return androidHome; + return androidHomeFinal; } - if (typeof androidHome === 'undefined') { + if (androidHome === undefined) { console.log( - ` ${colors.red(symbols().fail)} ANDROID_HOME environment variable is NOT set!'\n` + ` ${colors.red(symbols().fail)} ANDROID_HOME environment variable is NOT set!\n` ); } else { console.log( - ` ${colors.red(symbols().fail)} ANDROID_HOME is set to '${androidHome} which is NOT a valid path!'\n` + ` ${colors.red(symbols().fail)} ANDROID_HOME is set to '${androidHome}'${fromDotEnv} which is NOT a valid path!\n` ); } - // if real device, verifying if all the requirements are present in known locations or added to PATH beforehand (only applicable for adb). - return ''; } + async getSdkRootFromUser() { + const answers: {sdkRoot: string} = await prompt([ + { + type: 'input', + name: 'sdkRoot', + message: 'Where do you wish to verify/download Android SDKs?', + default: getDefaultAndroidSdkRoot(this.platform), + filter: (input: string) => path.resolve(this.rootDir, untildify(input)) + } + ]); + + const {sdkRoot} = answers; + + if (!this.otherInfo.androidHomeInGlobalEnv) { + // if ANDROID_HOME is already set in global env, saving it to .env is of no use. + // this is important if global ANDROID_HOME env is set to '', in which case we + // should not save the user supplied value to .env. + const envPath = path.join(this.rootDir, '.env'); + fs.appendFileSync(envPath, `\nANDROID_HOME=${sdkRoot}\n`); + } + + return sdkRoot; + } + getConfigFromOptions(options: {[key: string]: string | string[] | boolean}) { const configs: SetupConfigs = {}; @@ -479,4 +528,27 @@ export class AndroidSetup { return result; } + + sdkRootEnvSetInstructions() { + console.log(); + console.log(colors.red('IMPORTANT')); + console.log(colors.red('---------')); + + if (this.otherInfo.androidHomeInGlobalEnv && process.env.ANDROID_HOME === '') { + console.log(`${colors.cyan('ANDROID_HOME')} env is set to '' which is NOT a valid path!\n`); + console.log(`Please set ${colors.cyan('ANDROID_HOME')} to '${this.sdkRoot}' in your environment variables.`); + console.log('(As ANDROID_HOME env is already set, temporarily saving it to .env won\'t work.)\n'); + } else { + console.log( + `${colors.cyan('ANDROID_HOME')} env was temporarily saved in ${colors.cyan( + '.env' + )} file (set to '${this.sdkRoot}').\n` + ); + console.log(`Please set ${colors.cyan( + 'ANDROID_HOME' + )} env to '${this.sdkRoot}' globally and then delete it from ${colors.cyan('.env')} file.`); + } + + console.log('Doing this now might save you from future troubles.\n'); + } } diff --git a/src/commands/android/interfaces.ts b/src/commands/android/interfaces.ts index 3f8a820..1fe4165 100644 --- a/src/commands/android/interfaces.ts +++ b/src/commands/android/interfaces.ts @@ -4,6 +4,10 @@ export interface Options { export type Platform = 'windows' | 'linux' | 'mac'; +export interface OtherInfo { + androidHomeInGlobalEnv: boolean; +} + export interface SetupConfigs { mode?: 'real' | 'emulator' | 'both'; browsers?: 'chrome' | 'firefox' | 'both' | 'none'; diff --git a/src/commands/android/utils.ts b/src/commands/android/utils.ts index 5c2cc9d..c06c2d6 100644 --- a/src/commands/android/utils.ts +++ b/src/commands/android/utils.ts @@ -3,6 +3,7 @@ import cliProgress from 'cli-progress'; import download from 'download'; import fs from 'fs'; import path from 'path'; +import {homedir} from 'os'; import {execSync} from 'child_process'; import {copySync, rmDirSync, symbols} from '../../utils'; @@ -39,6 +40,23 @@ export const getAbiForOS = () => { return 'x86_64'; }; +export const getDefaultAndroidSdkRoot = (platform: Platform) => { + if (platform === 'windows') { + let basePath = process.env.LOCALAPPDATA; + if (!basePath) { + basePath = homedir(); + } + + return path.join(basePath, 'Android', 'sdk'); + } + + if (platform === 'linux') { + return path.join(homedir(), 'Android', 'Sdk'); + } + + return path.join(homedir(), 'Library', 'Android', 'sdk'); +}; + export const downloadAndSetupAndroidSdk = async (sdkRoot: string, platform: Platform) => { // make sure `cmdline-tools` folder is not present already const cmdline_tools = path.join(sdkRoot, 'cmdline-tools');