diff --git a/bin/templates/cordova/lib/check_reqs.js b/bin/templates/cordova/lib/check_reqs.js index 764bd594..21d90f76 100644 --- a/bin/templates/cordova/lib/check_reqs.js +++ b/bin/templates/cordova/lib/check_reqs.js @@ -153,10 +153,10 @@ module.exports.get_gradle_wrapper = function () { // Returns a promise. Called only by build and clean commands. module.exports.check_gradle = function () { - var sdkDir = process.env.ANDROID_HOME; + var sdkDir = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME; if (!sdkDir) { return Promise.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' + - 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.')); + 'Might need to install Android SDK or set up \'ANDROID_SDK_ROOT\' env variable.')); } var gradlePath = module.exports.get_gradle_wrapper(); @@ -242,40 +242,58 @@ module.exports.check_java = function () { // Returns a promise. module.exports.check_android = function () { return Promise.resolve().then(function () { - var androidCmdPath = forgivingWhichSync('android'); - var adbInPath = forgivingWhichSync('adb'); - var avdmanagerInPath = forgivingWhichSync('avdmanager'); - var hasAndroidHome = !!process.env.ANDROID_HOME && fs.existsSync(process.env.ANDROID_HOME); function maybeSetAndroidHome (value) { if (!hasAndroidHome && fs.existsSync(value)) { hasAndroidHome = true; - process.env.ANDROID_HOME = value; + process.env.ANDROID_SDK_ROOT = value; } } + + var androidCmdPath = forgivingWhichSync('android'); + var adbInPath = forgivingWhichSync('adb'); + var avdmanagerInPath = forgivingWhichSync('avdmanager'); + var hasAndroidHome = false; + + if (process.env.ANDROID_SDK_ROOT) { + maybeSetAndroidHome(path.resolve(process.env.ANDROID_SDK_ROOT)); + } + // First ensure ANDROID_HOME is set // If we have no hints (nothing in PATH), try a few default locations if (!hasAndroidHome && !androidCmdPath && !adbInPath && !avdmanagerInPath) { - if (process.env.ANDROID_SDK_ROOT) { - // Quick fix to set ANDROID_HOME according to ANDROID_SDK_ROOT - // if ANDROID_HOME is **not** defined and - // ANDROID_SDK_ROOT **is** defined - // according to environment variables as documented in: - // https://developer.android.com/studio/command-line/variables - maybeSetAndroidHome(path.join(process.env.ANDROID_SDK_ROOT)); + if (process.env.ANDROID_HOME) { + // Fallback to deprecated `ANDROID_HOME` variable + maybeSetAndroidHome(path.join(process.env.ANDROID_HOME)); } if (module.exports.isWindows()) { // Android Studio 1.0 installer - maybeSetAndroidHome(path.join(process.env.LOCALAPPDATA, 'Android', 'sdk')); - maybeSetAndroidHome(path.join(process.env.ProgramFiles, 'Android', 'sdk')); + if (process.env.LOCALAPPDATA) { + maybeSetAndroidHome(path.join(process.env.LOCALAPPDATA, 'Android', 'sdk')); + } + if (process.env.ProgramFiles) { + maybeSetAndroidHome(path.join(process.env.ProgramFiles, 'Android', 'sdk')); + } + // Android Studio pre-1.0 installer - maybeSetAndroidHome(path.join(process.env.LOCALAPPDATA, 'Android', 'android-studio', 'sdk')); - maybeSetAndroidHome(path.join(process.env.ProgramFiles, 'Android', 'android-studio', 'sdk')); + 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')); + } + // Stand-alone installer - maybeSetAndroidHome(path.join(process.env.LOCALAPPDATA, 'Android', 'android-sdk')); - maybeSetAndroidHome(path.join(process.env.ProgramFiles, 'Android', 'android-sdk')); + 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')); + } } else if (module.exports.isDarwin()) { // Android Studio 1.0 installer - maybeSetAndroidHome(path.join(process.env.HOME, 'Library', 'Android', 'sdk')); + if (process.env.HOME) { + maybeSetAndroidHome(path.join(process.env.HOME, 'Library', 'Android', 'sdk')); + } // Android Studio pre-1.0 installer maybeSetAndroidHome('/Applications/Android Studio.app/sdk'); // Stand-alone zip file that user might think to put under /Applications @@ -288,8 +306,9 @@ module.exports.check_android = function () { maybeSetAndroidHome(path.join(process.env.HOME, 'android-sdk')); } } + if (!hasAndroidHome) { - // If we dont have ANDROID_HOME, but we do have some tools on the PATH, try to infer from the tooling PATH. + // If we dont have ANDROID_SDK_ROOT, but we do have some tools on the PATH, try to infer from the tooling PATH. var parentDir, grandParentDir; if (androidCmdPath) { parentDir = path.dirname(androidCmdPath); @@ -297,7 +316,7 @@ module.exports.check_android = function () { if (path.basename(parentDir) === 'tools' || fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) { maybeSetAndroidHome(grandParentDir); } else { - throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting it manually.\n' + + throw new CordovaError('Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' + 'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' + 'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools directory.'); } @@ -308,7 +327,7 @@ module.exports.check_android = function () { if (path.basename(parentDir) === 'platform-tools') { maybeSetAndroidHome(grandParentDir); } else { - throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting it manually.\n' + + throw new CordovaError('Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' + '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.'); } @@ -319,29 +338,29 @@ module.exports.check_android = function () { if (path.basename(parentDir) === 'bin' && path.basename(grandParentDir) === 'tools') { maybeSetAndroidHome(path.dirname(grandParentDir)); } else { - throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting it manually.\n' + + throw new CordovaError('Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' + '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.'); } } } - if (!process.env.ANDROID_HOME) { - throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting it manually.\n' + + if (!process.env.ANDROID_SDK_ROOT) { + throw new CordovaError('Failed to find \'ANDROID_SDK_ROOT\' environment variable. Try setting it manually.\n' + 'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.'); } - if (!fs.existsSync(process.env.ANDROID_HOME)) { - throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env.ANDROID_HOME + + 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 + '\nTry update it manually to point to valid SDK directory.'); } // Next let's make sure relevant parts of the SDK tooling is in our PATH if (hasAndroidHome && !androidCmdPath) { - process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'tools'); + process.env.PATH += path.delimiter + path.join(process.env.ANDROID_SDK_ROOT, 'tools'); } if (hasAndroidHome && !adbInPath) { - process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'platform-tools'); + process.env.PATH += path.delimiter + path.join(process.env.ANDROID_SDK_ROOT, 'platform-tools'); } if (hasAndroidHome && !avdmanagerInPath) { - process.env.PATH += path.delimiter + path.join(process.env.ANDROID_HOME, 'tools', 'bin'); + process.env.PATH += path.delimiter + path.join(process.env.ANDROID_SDK_ROOT, 'tools', 'bin'); } return hasAndroidHome; }); @@ -386,10 +405,12 @@ module.exports.check_android_target = function (originalError) { // Returns a promise. module.exports.run = function () { + 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)'); + return Promise.all([this.check_java(), this.check_android()]).then(function (values) { - 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)'); + console.log('Using Android SDK: ' + process.env.ANDROID_SDK_ROOT); if (!String(values[0]).startsWith('1.8.')) { throw new CordovaError( diff --git a/framework/cordova.gradle b/framework/cordova.gradle index 03e243c2..4de776a4 100644 --- a/framework/cordova.gradle +++ b/framework/cordova.gradle @@ -81,7 +81,11 @@ String doFindLatestInstalledBuildTools(String minBuildToolsVersionString) { String getAndroidSdkDir() { def rootDir = project.rootDir def androidSdkDir = null - String envVar = System.getenv("ANDROID_HOME") + String envVar = System.getenv("ANDROID_SDK_ROOT") + if (envVar == null) { + envVar = System.getenv("ANDROID_HOME") + } + def localProperties = new File(rootDir, 'local.properties') String systemProperty = System.getProperty("android.home") if (envVar != null) { diff --git a/spec/unit/check_reqs.spec.js b/spec/unit/check_reqs.spec.js index 454c0d6d..ff6a5ea1 100644 --- a/spec/unit/check_reqs.spec.js +++ b/spec/unit/check_reqs.spec.js @@ -49,23 +49,23 @@ describe('check_reqs', function () { spyOn(which, 'sync').and.returnValue(null); spyOn(fs, 'existsSync').and.returnValue(true); }); - it('it should set ANDROID_HOME on Windows', () => { + it('it should set ANDROID_SDK_ROOT on Windows', () => { spyOn(check_reqs, 'isWindows').and.returnValue(true); process.env.LOCALAPPDATA = 'windows-local-app-data'; process.env.ProgramFiles = 'windows-program-files'; return check_reqs.check_android().then(function () { - expect(process.env.ANDROID_HOME).toContain('windows-local-app-data'); + expect(process.env.ANDROID_SDK_ROOT).toContain('windows-local-app-data'); }).finally(function () { delete process.env.LOCALAPPDATA; delete process.env.ProgramFiles; }); }); - it('it should set ANDROID_HOME on Darwin', () => { + it('it should set ANDROID_SDK_ROOT on Darwin', () => { spyOn(check_reqs, 'isWindows').and.returnValue(false); spyOn(check_reqs, 'isDarwin').and.returnValue(true); process.env.HOME = 'home is where the heart is'; return check_reqs.check_android().then(function () { - expect(process.env.ANDROID_HOME).toContain('home is where the heart is'); + expect(process.env.ANDROID_SDK_ROOT).toContain('home is where the heart is'); }).finally(function () { delete process.env.HOME; }); @@ -77,7 +77,7 @@ describe('check_reqs', function () { return path; }); }); - it('should set ANDROID_HOME based on `android` command if command exists in a SDK-like directory structure', () => { + it('should set ANDROID_SDK_ROOT based on `android` command if command exists in a SDK-like directory structure', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(which, 'sync').and.callFake(function (cmd) { if (cmd === 'android') { @@ -87,7 +87,7 @@ describe('check_reqs', function () { } }); return check_reqs.check_android().then(function () { - expect(process.env.ANDROID_HOME).toEqual('/android/sdk'); + expect(process.env.ANDROID_SDK_ROOT).toEqual('/android/sdk'); }); }); it('should error out if `android` command exists in a non-SDK-like directory structure', () => { @@ -105,7 +105,7 @@ describe('check_reqs', function () { expect(err.message).toContain('update your PATH to include valid path'); }); }); - it('should set ANDROID_HOME based on `adb` command if command exists in a SDK-like directory structure', () => { + it('should set ANDROID_SDK_ROOT based on `adb` command if command exists in a SDK-like directory structure', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(which, 'sync').and.callFake(function (cmd) { if (cmd === 'adb') { @@ -115,7 +115,7 @@ describe('check_reqs', function () { } }); return check_reqs.check_android().then(function () { - expect(process.env.ANDROID_HOME).toEqual('/android/sdk'); + expect(process.env.ANDROID_SDK_ROOT).toEqual('/android/sdk'); }); }); it('should error out if `adb` command exists in a non-SDK-like directory structure', () => { @@ -133,7 +133,7 @@ describe('check_reqs', function () { expect(err.message).toContain('update your PATH to include valid path'); }); }); - it('should set ANDROID_HOME based on `avdmanager` command if command exists in a SDK-like directory structure', () => { + it('should set ANDROID_SDK_ROOT based on `avdmanager` command if command exists in a SDK-like directory structure', () => { spyOn(fs, 'existsSync').and.returnValue(true); spyOn(which, 'sync').and.callFake(function (cmd) { if (cmd === 'avdmanager') { @@ -143,7 +143,7 @@ describe('check_reqs', function () { } }); return check_reqs.check_android().then(function () { - expect(process.env.ANDROID_HOME).toEqual('/android/sdk'); + expect(process.env.ANDROID_SDK_ROOT).toEqual('/android/sdk'); }); }); it('should error out if `avdmanager` command exists in a non-SDK-like directory structure', () => { @@ -163,14 +163,65 @@ describe('check_reqs', function () { }); }); }); + + describe('ANDROID_SDK_ROOT environment variable detection', () => { + beforeEach(() => { + delete process.env.ANDROID_SDK_ROOT; + delete process.env.ANDROID_HOME; + check_reqs.__set__('forgivingWhichSync', jasmine.createSpy().and.returnValue('')); + }); + + const expectedAndroidSdkPath = path.sep + 'android' + path.sep + 'sdk'; + const expectedAndroidRootSdkPath = path.sep + 'android' + path.sep + 'sdk' + path.sep + 'root'; + + it('should error if neither ANDROID_SDK_ROOT or ANDROID_HOME is defined', () => { + spyOn(fs, 'existsSync').and.returnValue(true); + return check_reqs.check_android().catch((error) => { + expect(error.toString()).toContain('Failed to find \'ANDROID_SDK_ROOT\' environment variable.'); + }); + }); + + it('should use ANDROID_SDK_ROOT if defined', () => { + spyOn(fs, 'existsSync').and.returnValue(true); + process.env.ANDROID_SDK_ROOT = '/android/sdk'; + return check_reqs.check_android().then(() => { + expect(process.env.ANDROID_SDK_ROOT).toContain(expectedAndroidSdkPath); + }); + }); + + it('should use ANDROID_HOME if defined and ANDROID_SDK_ROOT is not defined', () => { + spyOn(fs, 'existsSync').and.returnValue(true); + process.env.ANDROID_HOME = '/android/sdk'; + return check_reqs.check_android().then(() => { + expect(process.env.ANDROID_SDK_ROOT).toContain(expectedAndroidSdkPath); + }); + }); + + it('should use ANDROID_SDK_ROOT if defined and ANDROID_HOME is defined', () => { + spyOn(fs, 'existsSync').and.returnValue(true); + process.env.ANDROID_SDK_ROOT = '/android/sdk/root'; + process.env.ANDROID_HOME = '/android/sdk'; + return check_reqs.check_android().then(() => { + expect(process.env.ANDROID_SDK_ROOT).toContain(expectedAndroidRootSdkPath); + }); + }); + + it('should throw if ANDROID_SDK_ROOT points to an invalid path', () => { + process.env.ANDROID_SDK_ROOT = '/android/sdk'; + return check_reqs.check_android().catch((error) => { + expect(error.toString()).toContain('\'ANDROID_SDK_ROOT\' environment variable is set to non-existent path:'); + }); + }); + }); + describe('set PATH for various Android binaries if not available', function () { beforeEach(function () { spyOn(which, 'sync').and.returnValue(null); - process.env.ANDROID_HOME = 'let the children play'; + process.env.ANDROID_SDK_ROOT = 'let the children play'; spyOn(fs, 'existsSync').and.returnValue(true); }); afterEach(function () { - delete process.env.ANDROID_HOME; + delete process.env.ANDROID_SDK_ROOT; }); it('should add tools/bin,tools,platform-tools to PATH if `avdmanager`,`android`,`adb` is not found', () => { return check_reqs.check_android().then(function () { @@ -182,6 +233,51 @@ describe('check_reqs', function () { }); }); + describe('check_gradle', () => { + describe('environment variable checks', () => { + beforeEach(() => { + delete process.env.ANDROID_SDK_ROOT; + delete process.env.ANDROID_HOME; + spyOn(check_reqs, 'get_gradle_wrapper').and.callFake(() => { + return (process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME) + '/bin/gradle'; + }); + }); + + it('with ANDROID_SDK_ROOT / without ANDROID_HOME', () => { + process.env.ANDROID_SDK_ROOT = '/android/sdk/root'; + expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/root/bin/gradle'); + }); + + it('with ANDROID_SDK_ROOT / with ANDROID_HOME', () => { + process.env.ANDROID_SDK_ROOT = '/android/sdk/root'; + process.env.ANDROID_HOME = '/android/sdk/home'; + expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/root/bin/gradle'); + }); + + it('without ANDROID_SDK_ROOT / with ANDROID_HOME', () => { + process.env.ANDROID_HOME = '/android/sdk/home'; + expectAsync(check_reqs.check_gradle()).toBeResolvedTo('/android/sdk/home/bin/gradle'); + }); + + it('without ANDROID_SDK_ROOT / without ANDROID_HOME', () => { + return check_reqs.check_gradle().catch((error) => { + expect(error.toString()).toContain('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.'); + }); + }); + }); + + it('should error if sdk is installed but no gradle found', () => { + process.env.ANDROID_SDK_ROOT = '/android/sdk'; + spyOn(check_reqs, 'get_gradle_wrapper').and.callFake(() => { + return ''; + }); + + return check_reqs.check_gradle().catch((error) => { + expect(error.toString()).toContain('Could not find an installed version of Gradle'); + }); + }); + }); + describe('get_target', function () { var ConfigParser; var getPreferenceSpy;