2015-10-21 07:15:57 +08:00
/ *
Licensed to the Apache Software Foundation ( ASF ) under one
or more contributor license agreements . See the NOTICE file
distributed with this work for additional information
regarding copyright ownership . The ASF licenses this file
to you under the Apache License , Version 2.0 ( the
"License" ) ; you may not use this file except in compliance
with the License . You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing ,
software distributed under the License is distributed on an
"AS IS" BASIS , WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND , either express or implied . See the License for the
specific language governing permissions and limitations
under the License .
* /
2020-01-07 06:15:22 +08:00
const execa = require ( 'execa' ) ;
2022-04-18 09:39:54 +08:00
const path = require ( 'path' ) ;
const fs = require ( 'fs-extra' ) ;
2021-03-27 21:05:50 +08:00
const { forgivingWhichSync , isWindows , isDarwin } = require ( './utils' ) ;
const java = require ( './env/java' ) ;
2020-01-24 09:53:50 +08:00
const { CordovaError , ConfigParser , events } = require ( 'cordova-common' ) ;
2022-04-18 09:39:54 +08:00
const android _sdk = require ( './android_sdk' ) ;
2021-07-06 14:38:28 +08:00
const { SDK _VERSION } = require ( './gradle-config-defaults' ) ;
2015-10-21 07:15:57 +08:00
2021-03-27 21:05:50 +08:00
// Re-exporting these for backwards compatibility and for unit testing.
// TODO: Remove uses and use the ./utils module directly.
Object . assign ( module . exports , { isWindows , isDarwin } ) ;
2017-03-14 14:44:52 +08:00
2020-01-24 09:53:50 +08:00
/ * *
2021-07-13 14:51:20 +08:00
* @ param { string } projectRoot
2020-01-24 09:53:50 +08:00
* @ returns { string } The android target in format "android-${target}"
* /
2021-07-13 14:51:20 +08:00
module . exports . get _target = function ( projectRoot ) {
const userTargetSdkVersion = getUserTargetSdkVersion ( projectRoot ) ;
2020-01-24 09:53:50 +08:00
2021-07-06 14:38:28 +08:00
if ( userTargetSdkVersion && userTargetSdkVersion < SDK _VERSION ) {
events . emit ( 'warn' , ` android-targetSdkVersion should be greater than or equal to ${ SDK _VERSION } . ` ) ;
2015-10-21 07:15:57 +08:00
}
2020-01-24 09:53:50 +08:00
2021-07-06 14:38:28 +08:00
return ` android- ${ Math . max ( userTargetSdkVersion , SDK _VERSION ) } ` ;
} ;
2022-05-18 22:18:33 +08:00
/ * *
* @ param { string } projectRoot
* @ returns { string } The android target in format "android-${target}"
* /
module . exports . get _compile = function ( projectRoot ) {
const userTargetSdkVersion = getUserTargetSdkVersion ( projectRoot ) || SDK _VERSION ;
const userCompileSdkVersion = getUserCompileSdkVersion ( projectRoot ) || userTargetSdkVersion ;
module . exports . isCompileSdkValid ( userCompileSdkVersion , userTargetSdkVersion ) ;
return userCompileSdkVersion ;
} ;
module . exports . isCompileSdkValid = ( compileSdk , targetSdk ) => {
targetSdk = ( targetSdk || SDK _VERSION ) ;
compileSdk = ( compileSdk || targetSdk ) ;
const isValid = compileSdk >= targetSdk ;
if ( ! isValid ) {
events . emit ( 'warn' , ` The "android-compileSdkVersion" ( ${ compileSdk } ) should be greater than or equal to the "android-targetSdkVersion" ( ${ targetSdk } ). ` ) ;
}
return isValid ;
} ;
2021-07-13 14:51:20 +08:00
/ * *
* @ param { string } projectRoot
* @ returns { number } target sdk or 0 if undefined
* /
function getUserTargetSdkVersion ( projectRoot ) {
2020-01-24 09:53:50 +08:00
// If the repo config.xml file exists, find the desired targetSdkVersion.
2021-07-13 14:51:20 +08:00
// We need to use the cordova project's config.xml here, since the platform
// project's config.xml does not yet have the user's preferences when this
// function is called during `Api.createPlatform`.
const configFile = path . join ( projectRoot , '../../config.xml' ) ;
2021-07-06 14:38:28 +08:00
if ( ! fs . existsSync ( configFile ) ) return 0 ;
2020-01-24 09:53:50 +08:00
const configParser = new ConfigParser ( configFile ) ;
2021-07-06 14:38:28 +08:00
const targetSdkVersion = parseInt ( configParser . getPreference ( 'android-targetSdkVersion' , 'android' ) , 10 ) ;
return isNaN ( targetSdkVersion ) ? 0 : targetSdkVersion ;
}
2015-10-21 07:15:57 +08:00
2022-05-18 22:18:33 +08:00
/ * *
* @ param { string } projectRoot
* @ returns { number } target sdk or 0 if undefined
* /
function getUserCompileSdkVersion ( projectRoot ) {
// If the repo config.xml file exists, find the desired compileSdkVersion.
// We need to use the cordova project's config.xml here, since the platform
// project's config.xml does not yet have the user's preferences when this
// function is called during `Api.createPlatform`.
const configFile = path . join ( projectRoot , '../../config.xml' ) ;
if ( ! fs . existsSync ( configFile ) ) return 0 ;
const configParser = new ConfigParser ( configFile ) ;
const compileSdkVersion = parseInt ( configParser . getPreference ( 'android-compileSdkVersion' , 'android' ) , 10 ) ;
return isNaN ( compileSdkVersion ) ? 0 : compileSdkVersion ;
}
2017-06-14 02:42:20 +08:00
module . exports . get _gradle _wrapper = function ( ) {
2022-04-18 09:39:54 +08:00
let androidStudioPath ;
let i = 0 ;
let foundStudio = false ;
let program _dir ;
2017-10-31 04:17:18 +08:00
// OK, This hack only works on Windows, not on Mac OS or Linux. We will be deleting this eventually!
2017-10-31 01:39:59 +08:00
if ( module . exports . isWindows ( ) ) {
2022-04-18 09:39:54 +08:00
const result = execa . sync ( path . join ( _ _dirname , 'getASPath.bat' ) ) ;
2017-06-20 01:57:12 +08:00
// console.log('result.stdout =' + result.stdout.toString());
// console.log('result.stderr =' + result.stderr.toString());
if ( result . stderr . toString ( ) . length > 0 ) {
2022-04-18 09:39:54 +08:00
const androidPath = path . join ( process . env . ProgramFiles , 'Android' ) + '/' ;
2017-06-21 07:54:32 +08:00
if ( fs . existsSync ( androidPath ) ) {
program _dir = fs . readdirSync ( androidPath ) ;
while ( i < program _dir . length && ! foundStudio ) {
if ( program _dir [ i ] . startsWith ( 'Android Studio' ) ) {
foundStudio = true ;
2020-04-15 11:36:40 +08:00
androidStudioPath = path . join ( process . env . ProgramFiles , 'Android' , program _dir [ i ] , 'gradle' ) ;
2017-06-21 07:54:32 +08:00
} else { ++ i ; }
}
2017-03-27 16:47:32 +08:00
}
2017-06-20 01:57:12 +08:00
} else {
2017-06-21 07:54:32 +08:00
// console.log('got android studio path from registry');
// remove the (os independent) new line char at the end of stdout
// add gradle to match the above.
2017-06-20 01:57:12 +08:00
androidStudioPath = path . join ( result . stdout . toString ( ) . split ( '\r\n' ) [ 0 ] , 'gradle' ) ;
2017-06-21 07:54:32 +08:00
}
2017-03-09 10:09:49 +08:00
}
2017-03-15 07:01:32 +08:00
if ( androidStudioPath !== null && fs . existsSync ( androidStudioPath ) ) {
2022-04-18 09:39:54 +08:00
const dirs = fs . readdirSync ( androidStudioPath ) ;
2017-06-14 02:42:20 +08:00
if ( dirs [ 0 ] . split ( '-' ) [ 0 ] === 'gradle' ) {
2017-03-15 07:01:32 +08:00
return path . join ( androidStudioPath , dirs [ 0 ] , 'bin' , 'gradle' ) ;
}
2017-03-09 10:09:49 +08:00
} else {
2017-06-14 02:42:20 +08:00
// OK, let's try to check for Gradle!
2017-03-15 07:01:32 +08:00
return forgivingWhichSync ( 'gradle' ) ;
2017-03-09 10:09:49 +08:00
}
} ;
2015-10-21 07:15:57 +08:00
// Returns a promise. Called only by build and clean commands.
2017-06-14 02:42:20 +08:00
module . exports . check _gradle = function ( ) {
2022-04-18 09:39:54 +08:00
const sdkDir = process . env . ANDROID _SDK _ROOT || process . env . ANDROID _HOME ;
2017-06-14 02:42:20 +08:00
if ( ! sdkDir ) {
2020-01-07 20:22:59 +08:00
return Promise . reject ( new CordovaError ( 'Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
2020-04-16 10:43:17 +08:00
'Might need to install Android SDK or set up \'ANDROID_SDK_ROOT\' env variable.' ) ) ;
2017-06-14 02:42:20 +08:00
}
2015-10-21 07:15:57 +08:00
2022-04-18 09:39:54 +08:00
const gradlePath = module . exports . get _gradle _wrapper ( ) ;
2020-01-07 20:22:59 +08:00
if ( gradlePath . length !== 0 ) return Promise . resolve ( gradlePath ) ;
return Promise . reject ( new CordovaError ( 'Could not find an installed version of Gradle either in Android Studio,\n' +
'or on your system to install the gradle wrapper. Please include gradle \n' +
'in your path, or install Android Studio' ) ) ;
2015-10-21 07:15:57 +08:00
} ;
2021-03-27 21:05:50 +08:00
/ * *
* Checks for the java installation and correct version
2021-03-30 21:40:34 +08:00
*
* Despite the name , it should return the Java version value , it ' s used by the Cordova CLI .
2021-03-27 21:05:50 +08:00
* /
module . exports . check _java = async function ( ) {
const javaVersion = await java . getVersion ( ) ;
2021-03-30 21:40:34 +08:00
return javaVersion ;
2015-10-21 07:15:57 +08:00
} ;
// Returns a promise.
2017-06-14 02:42:20 +08:00
module . exports . check _android = function ( ) {
2020-01-07 20:22:59 +08:00
return Promise . resolve ( ) . then ( function ( ) {
2022-04-18 09:39:54 +08:00
let hasAndroidHome = false ;
2017-06-14 02:42:20 +08:00
function maybeSetAndroidHome ( value ) {
2015-10-21 07:15:57 +08:00
if ( ! hasAndroidHome && fs . existsSync ( value ) ) {
hasAndroidHome = true ;
2020-04-16 10:43:17 +08:00
process . env . ANDROID _SDK _ROOT = value ;
2015-10-21 07:15:57 +08:00
}
}
2020-04-16 10:43:17 +08:00
2022-04-18 09:39:54 +08:00
const adbInPath = forgivingWhichSync ( 'adb' ) ;
const avdmanagerInPath = forgivingWhichSync ( 'avdmanager' ) ;
2020-04-16 10:43:17 +08:00
if ( process . env . ANDROID _SDK _ROOT ) {
maybeSetAndroidHome ( path . resolve ( process . env . ANDROID _SDK _ROOT ) ) ;
}
2017-03-14 14:44:52 +08:00
// First ensure ANDROID_HOME is set
// If we have no hints (nothing in PATH), try a few default locations
2021-04-13 18:16:43 +08:00
if ( ! hasAndroidHome && ! adbInPath && ! avdmanagerInPath ) {
2020-04-16 10:43:17 +08:00
if ( process . env . ANDROID _HOME ) {
// Fallback to deprecated `ANDROID_HOME` variable
maybeSetAndroidHome ( path . join ( process . env . ANDROID _HOME ) ) ;
2019-02-12 22:20:17 +08:00
}
2017-03-14 14:44:52 +08:00
if ( module . exports . isWindows ( ) ) {
2015-10-21 07:15:57 +08:00
// Android Studio 1.0 installer
2020-04-16 10:43:17 +08:00
if ( process . env . LOCALAPPDATA ) {
maybeSetAndroidHome ( path . join ( process . env . LOCALAPPDATA , 'Android' , 'sdk' ) ) ;
}
if ( process . env . ProgramFiles ) {
maybeSetAndroidHome ( path . join ( process . env . ProgramFiles , 'Android' , 'sdk' ) ) ;
}
2015-10-21 07:15:57 +08:00
// Android Studio pre-1.0 installer
2020-04-16 10:43:17 +08:00
if ( process . env . LOCALAPPDATA ) {
maybeSetAndroidHome ( path . join ( process . env . LOCALAPPDATA , 'Android' , 'android-studio' , 'sdk' ) ) ;
}
if ( process . env . ProgramFiles ) {
maybeSetAndroidHome ( path . join ( process . env . ProgramFiles , 'Android' , 'android-studio' , 'sdk' ) ) ;
}
2015-10-21 07:15:57 +08:00
// Stand-alone installer
2020-04-16 10:43:17 +08:00
if ( process . env . LOCALAPPDATA ) {
maybeSetAndroidHome ( path . join ( process . env . LOCALAPPDATA , 'Android' , 'android-sdk' ) ) ;
}
if ( process . env . ProgramFiles ) {
maybeSetAndroidHome ( path . join ( process . env . ProgramFiles , 'Android' , 'android-sdk' ) ) ;
}
2017-03-14 14:44:52 +08:00
} else if ( module . exports . isDarwin ( ) ) {
2015-10-21 07:15:57 +08:00
// Android Studio 1.0 installer
2020-04-16 10:43:17 +08:00
if ( process . env . HOME ) {
maybeSetAndroidHome ( path . join ( process . env . HOME , 'Library' , 'Android' , 'sdk' ) ) ;
}
2015-10-21 07:15:57 +08:00
// Android Studio pre-1.0 installer
maybeSetAndroidHome ( '/Applications/Android Studio.app/sdk' ) ;
// Stand-alone zip file that user might think to put under /Applications
maybeSetAndroidHome ( '/Applications/android-sdk-macosx' ) ;
maybeSetAndroidHome ( '/Applications/android-sdk' ) ;
}
2020-04-15 11:36:40 +08:00
if ( process . env . HOME ) {
2015-10-21 07:15:57 +08:00
// Stand-alone zip file that user might think to put under their home directory
2020-04-15 11:36:40 +08:00
maybeSetAndroidHome ( path . join ( process . env . HOME , 'android-sdk-macosx' ) ) ;
maybeSetAndroidHome ( path . join ( process . env . HOME , 'android-sdk' ) ) ;
2015-10-21 07:15:57 +08:00
}
}
2020-04-16 10:43:17 +08:00
2017-03-14 14:44:52 +08:00
if ( ! hasAndroidHome ) {
2020-04-16 10:43:17 +08:00
// If we dont have ANDROID_SDK_ROOT, but we do have some tools on the PATH, try to infer from the tooling PATH.
2022-04-18 09:39:54 +08:00
let parentDir , grandParentDir ;
2017-03-14 14:44:52 +08:00
if ( adbInPath ) {
parentDir = path . dirname ( adbInPath ) ;
grandParentDir = path . dirname ( parentDir ) ;
2017-06-14 02:42:20 +08:00
if ( path . basename ( parentDir ) === 'platform-tools' ) {
2017-03-14 14:44:52 +08:00
maybeSetAndroidHome ( grandParentDir ) ;
} else {
2020-04-16 10:43:17 +08:00
throw new CordovaError ( 'Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' +
2017-03-14 14:44:52 +08:00
'Detected \'adb\' command at ' + parentDir + ' but no \'platform-tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path . sep + 'platform-tools directory.' ) ;
}
}
if ( avdmanagerInPath ) {
parentDir = path . dirname ( avdmanagerInPath ) ;
grandParentDir = path . dirname ( parentDir ) ;
2017-06-14 02:42:20 +08:00
if ( path . basename ( parentDir ) === 'bin' && path . basename ( grandParentDir ) === 'tools' ) {
2017-03-14 14:44:52 +08:00
maybeSetAndroidHome ( path . dirname ( grandParentDir ) ) ;
} else {
2020-04-16 10:43:17 +08:00
throw new CordovaError ( 'Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' +
2017-03-14 14:44:52 +08:00
'Detected \'avdmanager\' command at ' + parentDir + ' but no \'tools' + path . sep + 'bin\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path . sep + 'tools' + path . sep + 'bin directory.' ) ;
}
2015-10-21 07:15:57 +08:00
}
}
2020-04-16 10:43:17 +08:00
if ( ! process . env . ANDROID _SDK _ROOT ) {
throw new CordovaError ( 'Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' +
2015-10-21 07:15:57 +08:00
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.' ) ;
}
2020-04-16 10:43:17 +08:00
if ( ! fs . existsSync ( process . env . ANDROID _SDK _ROOT ) ) {
throw new CordovaError ( '\'ANDROID_SDK_ROOT\' environment variable is set to non-existent path: ' + process . env . ANDROID _SDK _ROOT +
2015-10-21 07:15:57 +08:00
'\nTry update it manually to point to valid SDK directory.' ) ;
}
2017-03-14 14:44:52 +08:00
// Next let's make sure relevant parts of the SDK tooling is in our PATH
if ( hasAndroidHome && ! adbInPath ) {
2020-04-16 10:43:17 +08:00
process . env . PATH += path . delimiter + path . join ( process . env . ANDROID _SDK _ROOT , 'platform-tools' ) ;
2017-03-14 14:44:52 +08:00
}
if ( hasAndroidHome && ! avdmanagerInPath ) {
2020-04-16 10:43:17 +08:00
process . env . PATH += path . delimiter + path . join ( process . env . ANDROID _SDK _ROOT , 'tools' , 'bin' ) ;
2017-03-14 14:44:52 +08:00
}
2016-09-28 05:38:07 +08:00
return hasAndroidHome ;
2015-10-21 07:15:57 +08:00
} ) ;
} ;
2021-07-13 14:51:20 +08:00
module . exports . check _android _target = function ( projectRoot ) {
2015-10-21 07:15:57 +08:00
// valid_target can look like:
// android-19
// android-L
// Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20
2022-04-18 09:39:54 +08:00
const desired _api _level = module . exports . get _target ( projectRoot ) ;
2017-06-14 02:42:20 +08:00
return android _sdk . list _targets ( ) . then ( function ( targets ) {
2017-03-15 07:18:37 +08:00
if ( targets . indexOf ( desired _api _level ) >= 0 ) {
2015-10-21 07:15:57 +08:00
return targets ;
}
2021-07-06 19:33:26 +08:00
throw new CordovaError ( ` Please install the Android SDK Platform "platforms; ${ desired _api _level } " ` ) ;
2015-10-21 07:15:57 +08:00
} ) ;
} ;
// Returns a promise.
2017-06-14 02:42:20 +08:00
module . exports . run = function ( ) {
2020-04-16 10:43:17 +08:00
console . log ( 'Checking Java JDK and Android SDK versions' ) ;
console . log ( 'ANDROID_SDK_ROOT=' + process . env . ANDROID _SDK _ROOT + ' (recommended setting)' ) ;
console . log ( 'ANDROID_HOME=' + process . env . ANDROID _HOME + ' (DEPRECATED)' ) ;
2020-01-07 20:22:59 +08:00
return Promise . all ( [ this . check _java ( ) , this . check _android ( ) ] ) . then ( function ( values ) {
2020-04-16 10:43:17 +08:00
console . log ( 'Using Android SDK: ' + process . env . ANDROID _SDK _ROOT ) ;
2016-09-28 05:38:07 +08:00
2017-06-14 02:42:20 +08:00
if ( ! values [ 1 ] ) {
2019-01-27 23:52:22 +08:00
throw new CordovaError ( 'Requirements check failed for Android SDK! Android SDK was not detected.' ) ;
2017-06-14 02:42:20 +08:00
}
} ) ;
2015-10-21 07:15:57 +08:00
} ;
/ * *
* Object thar represents one of requirements for current platform .
* @ param { String } id The unique identifier for this requirements .
* @ param { String } name The name of requirements . Human - readable field .
* @ param { String } version The version of requirement installed . In some cases could be an array of strings
* ( for example , check _android _target returns an array of android targets installed )
* @ param { Boolean } installed Indicates whether the requirement is installed or not
* /
2022-04-18 09:39:54 +08:00
const Requirement = function ( id , name , version , installed ) {
2015-10-21 07:15:57 +08:00
this . id = id ;
this . name = name ;
this . installed = installed || false ;
this . metadata = {
2017-06-14 02:42:20 +08:00
version : version
2015-10-21 07:15:57 +08:00
} ;
} ;
/ * *
* Methods that runs all checks one by one and returns a result of checks
* as an array of Requirement objects . This method intended to be used by cordova - lib check _reqs method
*
2021-07-13 14:51:20 +08:00
* @ param { string } projectRoot
2015-10-21 07:15:57 +08:00
* @ return Promise < Requirement [ ] > Array of requirements . Due to implementation , promise is always fulfilled .
* /
2021-07-13 14:51:20 +08:00
module . exports . check _all = function ( projectRoot ) {
2022-04-18 09:39:54 +08:00
const requirements = [
2015-10-21 07:15:57 +08:00
new Requirement ( 'java' , 'Java JDK' ) ,
new Requirement ( 'androidSdk' , 'Android SDK' ) ,
new Requirement ( 'androidTarget' , 'Android target' ) ,
new Requirement ( 'gradle' , 'Gradle' )
] ;
2022-04-18 09:39:54 +08:00
const checkFns = [
2015-10-21 07:15:57 +08:00
this . check _java ,
this . check _android ,
2021-07-13 14:51:20 +08:00
this . check _android _target . bind ( this , projectRoot ) ,
2015-10-21 07:15:57 +08:00
this . check _gradle
] ;
// Then execute requirement checks one-by-one
return checkFns . reduce ( function ( promise , checkFn , idx ) {
// Update each requirement with results
2022-04-18 09:39:54 +08:00
const requirement = requirements [ idx ] ;
2017-06-14 02:42:20 +08:00
return promise . then ( checkFn ) . then ( function ( version ) {
2015-10-21 07:15:57 +08:00
requirement . installed = true ;
requirement . metadata . version = version ;
} , function ( err ) {
requirement . metadata . reason = err instanceof Error ? err . message : err ;
} ) ;
2020-01-07 20:22:59 +08:00
} , Promise . resolve ( ) ) . then ( function ( ) {
2015-10-21 07:15:57 +08:00
// When chain is completed, return requirements array to upstream API
return requirements ;
} ) ;
} ;