@@ -19,25 +19,31 @@ import {
1919 downloadFirefoxAndroid , downloadWithProgressBar , getAllAvailableOptions ,
2020 getBinaryLocation , getBinaryNameForOS , getFirefoxApkName , getLatestVersion
2121} from './utils/common' ;
22- import { downloadAndSetupAndroidSdk , execBinarySync , getDefaultAndroidSdkRoot , installPackagesUsingSdkManager } from './utils/sdk' ;
22+ import {
23+ downloadAndSetupAndroidSdk , downloadSdkBuildTools , execBinarySync ,
24+ getBuildToolsAvailableVersions , getDefaultAndroidSdkRoot , installPackagesUsingSdkManager
25+ } from './utils/sdk' ;
2326
2427import DOWNLOADS from './downloads.json' ;
2528
2629
2730export class AndroidSetup {
2831 sdkRoot : string ;
32+ javaHome : string ;
2933 options : Options ;
3034 rootDir : string ;
3135 platform : Platform ;
3236 otherInfo : OtherInfo ;
3337
34- constructor ( options : Options , rootDir = process . cwd ( ) ) {
38+ constructor ( options : Options = { } , rootDir = process . cwd ( ) ) {
3539 this . sdkRoot = '' ;
40+ this . javaHome = '' ;
3641 this . options = options ;
3742 this . rootDir = rootDir ;
3843 this . platform = getPlatformName ( ) ;
3944 this . otherInfo = {
40- androidHomeInGlobalEnv : false
45+ androidHomeInGlobalEnv : false ,
46+ javaHomeInGlobalEnv : false
4147 } ;
4248 }
4349
@@ -56,14 +62,52 @@ export class AndroidSetup {
5662 return false ;
5763 }
5864
59- let result = true ;
65+ this . loadEnvFromDotEnv ( ) ;
66+
67+ let javaHomeFound : boolean | null = false ;
68+ if ( this . options . appium ) {
69+ javaHomeFound = this . isJavaHomeEnvSet ( ) ;
70+
71+ if ( ! javaHomeFound ) {
72+ this . javaHomeNotFoundInstructions ( ) ;
73+
74+ if ( javaHomeFound === false ) {
75+ Logger . log ( `${ colors . red (
76+ 'ERROR:'
77+ ) } JAVA_HOME env variable could not be set in a .env file. Please set the JAVA_HOME env variable as instructed above.`) ;
78+
79+ this . envSetHelp ( ) ;
80+
81+ return false ;
82+ }
83+
84+ // env can be set in dotenv file (javaHomeFound=null).
85+ process . env . JAVA_HOME = await this . getJavaHomeFromUser ( ) ;
86+ }
87+
88+ this . javaHome = process . env . JAVA_HOME || '' ;
89+ }
6090
6191 const sdkRootEnv = this . getSdkRootFromEnv ( ) ;
62- this . sdkRoot = sdkRootEnv || await this . getSdkRootFromUser ( ) ;
6392
64- const originalAndroidHome = process . env . ANDROID_HOME ;
93+ if ( this . options . appium && ! sdkRootEnv && this . otherInfo . androidHomeInGlobalEnv ) {
94+ // ANDROID_HOME is set to an invalid path in system env. We can get around this for mobile-web
95+ // since ANDROID_HOME is not a mandatory requirement there and Nightwatch would complain when it
96+ // is required, but for Appium to work properly, it should be set to correct path in sys env or .env.
97+ Logger . log ( `${ colors . red ( 'ERROR:' ) } For Appium to work properly, ${ colors . cyan (
98+ 'ANDROID_HOME'
99+ ) } env variable must be set to a valid path in your system environment variables.`) ;
100+
101+ this . envSetHelp ( ) ;
102+
103+ return false ;
104+ }
105+
106+ this . sdkRoot = sdkRootEnv || await this . getSdkRootFromUser ( ) ;
65107 process . env . ANDROID_HOME = this . sdkRoot ;
66108
109+ let result = true ;
110+
67111 const setupConfigs : SetupConfigs = await this . getSetupConfigs ( this . options ) ;
68112 Logger . log ( ) ;
69113
@@ -94,10 +138,8 @@ export class AndroidSetup {
94138 Logger . log ( `${ colors . bold ( 'Note:' ) } Please make sure you have required browsers installed on your real-device before running tests.\n` ) ;
95139 }
96140
97- process . env . ANDROID_HOME = originalAndroidHome ;
98-
99- if ( ! sdkRootEnv ) {
100- this . sdkRootEnvSetInstructions ( ) ;
141+ if ( ! sdkRootEnv || ( this . options . appium && ! javaHomeFound ) ) {
142+ this . envSetInstructions ( sdkRootEnv ) ;
101143 }
102144
103145 return {
@@ -162,7 +204,7 @@ export class AndroidSetup {
162204
163205 return true ;
164206 } catch {
165- Logger . log ( `${ colors . red ( 'Error:' ) } Java Development Kit is required to work with Android SDKs. Download from here:` ) ;
207+ Logger . log ( `${ colors . red ( 'Error:' ) } Java Development Kit v9 or above is required to work with Android SDKs. Download from here:` ) ;
166208 Logger . log ( colors . cyan ( ' https://www.oracle.com/java/technologies/downloads/' ) , '\n' ) ;
167209
168210 Logger . log ( `Make sure Java is installed by running ${ colors . green ( 'java -version' ) } command and then re-run this tool.\n` ) ;
@@ -171,12 +213,119 @@ export class AndroidSetup {
171213 }
172214 }
173215
174- getSdkRootFromEnv ( ) : string {
175- Logger . log ( 'Checking the value of ANDROID_HOME environment variable...' ) ;
176-
216+ loadEnvFromDotEnv ( ) : void {
177217 this . otherInfo . androidHomeInGlobalEnv = 'ANDROID_HOME' in process . env ;
178218
219+ if ( this . options . appium ) {
220+ this . otherInfo . javaHomeInGlobalEnv = 'JAVA_HOME' in process . env ;
221+ }
222+
179223 dotenv . config ( { path : path . join ( this . rootDir , '.env' ) } ) ;
224+ }
225+
226+ isJavaHomeEnvSet ( ) : boolean | null {
227+ Logger . log ( 'Checking the value of JAVA_HOME environment variable...' ) ;
228+
229+ const javaHome = process . env . JAVA_HOME ;
230+ const fromDotEnv = this . otherInfo . javaHomeInGlobalEnv ? '' : ' (taken from .env)' ;
231+
232+ if ( javaHome !== undefined && fs . existsSync ( javaHome ) ) {
233+ Logger . log ( ` ${ colors . green ( symbols ( ) . ok ) } JAVA_HOME is set to '${ javaHome } '${ fromDotEnv } ` ) ;
234+
235+ const javaHomeBin = path . resolve ( javaHome , 'bin' ) ;
236+ if ( fs . existsSync ( javaHomeBin ) ) {
237+ Logger . log ( ` ${ colors . green ( symbols ( ) . ok ) } 'bin' subfolder exists under '${ javaHome } '\n` ) ;
238+
239+ return true ;
240+ }
241+
242+ Logger . log ( ` ${ colors . red ( symbols ( ) . fail ) } 'bin' subfolder does not exist under '${ javaHome } '. Is ${ javaHome } set to a proper value?\n` ) ;
243+ if ( this . otherInfo . javaHomeInGlobalEnv ) {
244+ // we cannot set it ourselves
245+ return false ;
246+ }
247+
248+ // We can set the env in dotenv file.
249+ return null ;
250+ }
251+
252+ if ( javaHome === undefined ) {
253+ Logger . log ( ` ${ colors . red ( symbols ( ) . fail ) } JAVA_HOME env variable is NOT set!\n` ) ;
254+ } else {
255+ Logger . log ( ` ${ colors . red ( symbols ( ) . fail ) } JAVA_HOME is set to '${ javaHome } '${ fromDotEnv } but this is NOT a valid path!\n` ) ;
256+
257+ if ( this . otherInfo . javaHomeInGlobalEnv ) {
258+ // we cannot set it ourselves
259+ return false ;
260+ }
261+ }
262+
263+ // we can set the env in dotenv file.
264+ return null ;
265+ }
266+
267+ javaHomeNotFoundInstructions ( ) : void {
268+ Logger . log ( `${ colors . red ( 'NOTE:' ) } For Appium to work properly, ${ colors . cyan (
269+ 'JAVA_HOME'
270+ ) } env variable must be set to the root folder path of your local JDK installation.`) ;
271+
272+ let expectedPath ;
273+
274+ if ( this . platform === 'windows' ) {
275+ expectedPath = 'C:\\Program Files\\Java\\jdk1.8.0_111' ;
276+ } else if ( this . platform === 'mac' ) {
277+ expectedPath = '/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home' ;
278+ } else {
279+ expectedPath = '/usr/lib/jvm/java-8-oracle' ;
280+ }
281+
282+ Logger . log ( `${ colors . green ( 'Hint:' ) } On a ${ this . platform } system, the JDK installation path should be something similar to '${ expectedPath } '.\n` ) ;
283+ }
284+
285+ async getJavaHomeFromUser ( ) : Promise < string > {
286+ let javaHome ;
287+
288+ if ( this . platform === 'mac' ) {
289+ try {
290+ const stdout = execSync ( '/usr/libexec/java_home' , {
291+ stdio : 'pipe'
292+ } ) ;
293+
294+ javaHome = stdout . toString ( ) ;
295+
296+ Logger . log ( `Auto-detected JAVA_HOME to be: ${ colors . green ( javaHome ) } ` ) ;
297+ // eslint-disable-next-line
298+ } catch { }
299+ }
300+
301+ const answers : { javaHome : string } = await prompt ( [
302+ {
303+ type : 'input' ,
304+ name : 'javaHome' ,
305+ message : 'Enter the path to the root folder of your local JDK installation:' ,
306+ validate : ( input ) => {
307+ if ( ! fs . existsSync ( input ) ) {
308+ return 'Entered path does not exist' ;
309+ }
310+
311+ if ( ! fs . existsSync ( path . resolve ( input , 'bin' ) ) ) {
312+ return `'bin' subfolder does not exist under '${ input } '` ;
313+ }
314+
315+ return true ;
316+ }
317+ }
318+ ] , { javaHome} ) ;
319+ Logger . log ( ) ;
320+
321+ const envPath = path . join ( this . rootDir , '.env' ) ;
322+ fs . appendFileSync ( envPath , `\nJAVA_HOME=${ answers . javaHome } ` ) ;
323+
324+ return answers . javaHome ;
325+ }
326+
327+ getSdkRootFromEnv ( ) : string {
328+ Logger . log ( 'Checking the value of ANDROID_HOME environment variable...' ) ;
180329
181330 const androidHome = process . env . ANDROID_HOME ;
182331 const fromDotEnv = this . otherInfo . androidHomeInGlobalEnv ? '' : ' (taken from .env)' ;
@@ -228,7 +377,7 @@ export class AndroidSetup {
228377 // this is important if global ANDROID_HOME env is set to '', in which case we
229378 // should not save the user supplied value to .env.
230379 const envPath = path . join ( this . rootDir , '.env' ) ;
231- fs . appendFileSync ( envPath , `\nANDROID_HOME=${ sdkRoot } \n ` ) ;
380+ fs . appendFileSync ( envPath , `\nANDROID_HOME=${ sdkRoot } ` ) ;
232381 }
233382
234383 return sdkRoot ;
@@ -406,6 +555,23 @@ export class AndroidSetup {
406555 const missingBinaries = this . checkBinariesPresent ( requiredBinaries ) ;
407556 missingRequirements . push ( ...missingBinaries ) ;
408557
558+ // check for build-tools
559+ if ( this . options . appium ) {
560+ const buildToolsPath = path . join ( this . sdkRoot , 'build-tools' ) ;
561+ const availableVersions = getBuildToolsAvailableVersions ( buildToolsPath ) ;
562+ if ( availableVersions . length > 0 ) {
563+ Logger . log (
564+ ` ${ colors . green ( symbols ( ) . ok ) } ${ colors . cyan ( 'Android Build Tools' ) } present at '${ buildToolsPath } '.` ,
565+ `Available versions: ${ colors . cyan ( availableVersions . join ( ', ' ) ) } \n`
566+ ) ;
567+ } else {
568+ Logger . log (
569+ ` ${ colors . red ( symbols ( ) . fail ) } ${ colors . cyan ( 'Android Build Tools' ) } not present at '${ buildToolsPath } '\n`
570+ ) ;
571+ missingRequirements . push ( 'build-tools' ) ;
572+ }
573+ }
574+
409575 // check for platforms subdirectory (required by emulator)
410576 if ( requiredBinaries . includes ( 'emulator' ) ) {
411577 const platormsPath = path . join ( this . sdkRoot , 'platforms' ) ;
@@ -488,6 +654,17 @@ export class AndroidSetup {
488654 packagesToInstall
489655 ) ;
490656
657+ // Download build-tools if using Appium
658+ if ( this . options . appium ) {
659+ const res = downloadSdkBuildTools (
660+ getBinaryLocation ( this . sdkRoot , this . platform , 'sdkmanager' , true ) ,
661+ this . platform
662+ ) ;
663+ if ( ! res ) {
664+ result = false ;
665+ }
666+ }
667+
491668 if ( missingRequirements . includes ( 'platforms' ) ) {
492669 Logger . log ( 'Creating platforms subdirectory...' ) ;
493670
@@ -814,25 +991,49 @@ export class AndroidSetup {
814991 }
815992 }
816993
817- sdkRootEnvSetInstructions ( ) {
994+ envSetInstructions ( sdkRootEnv : string ) {
818995 Logger . log ( colors . red ( 'IMPORTANT' ) ) ;
819996 Logger . log ( colors . red ( '---------' ) ) ;
820997
821- if ( this . otherInfo . androidHomeInGlobalEnv && process . env . ANDROID_HOME === '' ) {
822- Logger . log ( `${ colors . cyan ( 'ANDROID_HOME' ) } env is set to '' which is NOT a valid path!\n` ) ;
823- Logger . log ( `Please set ${ colors . cyan ( 'ANDROID_HOME' ) } to '${ this . sdkRoot } ' in your environment variables.` ) ;
824- Logger . log ( '(As ANDROID_HOME env is already set, temporarily saving it to .env won\'t work.)\n' ) ;
825- } else {
998+ if ( ! sdkRootEnv ) {
999+ // ANDROID_HOME is either undefined or '' in system env and .env.
1000+ if ( this . otherInfo . androidHomeInGlobalEnv ) {
1001+ // ANDROID_HOME is set in system env, to ''.
1002+ Logger . log ( `${ colors . cyan ( 'ANDROID_HOME' ) } env is set to '' which is NOT a valid path!\n` ) ;
1003+ Logger . log ( `Please set ${ colors . cyan ( 'ANDROID_HOME' ) } to '${ this . sdkRoot } ' in your system environment variables.` ) ;
1004+ Logger . log ( '(As ANDROID_HOME is already set in system env, temporarily saving it to .env file won\'t work.)\n' ) ;
1005+ } else {
1006+ Logger . log (
1007+ `${ colors . cyan ( 'ANDROID_HOME' ) } env was temporarily saved in ${ colors . cyan (
1008+ '.env'
1009+ ) } file (set to '${ this . sdkRoot } ').\n`
1010+ ) ;
1011+ Logger . log ( `Please set ${ colors . cyan (
1012+ 'ANDROID_HOME'
1013+ ) } env to '${ this . sdkRoot } ' in your system environment variables and then delete it from ${ colors . cyan ( '.env' ) } file.\n`) ;
1014+ }
1015+ }
1016+
1017+ if ( this . options . appium ) {
1018+ // JAVA_HOME env was not found and we set it ourselves in .env (javaHomeFound=null).
1019+ // In case JAVA_HOME was found incorrect in system env (javaHomeFound=false),
1020+ // process should have exited with error.
8261021 Logger . log (
827- `${ colors . cyan ( 'ANDROID_HOME ' ) } env was temporarily saved in ${ colors . cyan (
1022+ `${ colors . cyan ( 'JAVA_HOME ' ) } env was temporarily saved in ${ colors . cyan (
8281023 '.env'
829- ) } file (set to '${ this . sdkRoot } ').\n`
1024+ ) } file (set to '${ this . javaHome } ').\n`
8301025 ) ;
8311026 Logger . log ( `Please set ${ colors . cyan (
832- 'ANDROID_HOME '
833- ) } env to '${ this . sdkRoot } ' globally and then delete it from ${ colors . cyan ( '.env' ) } file.`) ;
1027+ 'JAVA_HOME '
1028+ ) } env to '${ this . javaHome } ' in your system environment variables and then delete it from ${ colors . cyan ( '.env' ) } file.\n `) ;
8341029 }
8351030
836- Logger . log ( 'Doing this now might save you from future troubles.\n' ) ;
1031+ Logger . log ( 'Following the above instructions might save you from future troubles.\n' ) ;
1032+
1033+ this . envSetHelp ( ) ;
1034+ }
1035+
1036+ envSetHelp ( ) {
1037+ // Add platform-wise help or link a doc to help users set env variable.
8371038 }
8381039}
0 commit comments