diff --git a/bin/templates/cordova/Api.js b/bin/templates/cordova/Api.js index 3d80ddc3..6de476ac 100644 --- a/bin/templates/cordova/Api.js +++ b/bin/templates/cordova/Api.js @@ -34,6 +34,7 @@ var PluginManager = require('cordova-common').PluginManager; var CordovaLogger = require('cordova-common').CordovaLogger; var selfEvents = require('cordova-common').events; var ConfigParser = require('cordova-common').ConfigParser; +const prepare = require('./lib/prepare').prepare; var PLATFORM = 'android'; @@ -120,7 +121,7 @@ class Api { prepare (cordovaProject, prepareOptions) { cordovaProject.projectConfig = new ConfigParser(cordovaProject.locations.rootConfigXml || cordovaProject.projectConfig.path); - return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions); + return prepare.call(this, cordovaProject, prepareOptions); } /** diff --git a/bin/templates/cordova/lib/config/GradlePropertiesParser.js b/bin/templates/cordova/lib/config/GradlePropertiesParser.js index 14302d37..2c8aaa8d 100644 --- a/bin/templates/cordova/lib/config/GradlePropertiesParser.js +++ b/bin/templates/cordova/lib/config/GradlePropertiesParser.js @@ -90,8 +90,15 @@ class GradlePropertiesParser { this.gradleFile.set(key, properties[key]); } else if (value !== properties[key]) { if (this._defaults[key] && this._defaults[key] !== properties[key]) { - // Since the value does not match default, we will notify the discrepancy with Cordova's recommended value. - events.emit('info', `[Gradle Properties] Detected Gradle property "${key}" with the value of "${properties[key]}", Cordova's recommended value is "${this._defaults[key]}"`); + let shouldEmit = true; + if (key === 'org.gradle.jvmargs') { + shouldEmit = this._isJVMMemoryLessThanRecommended(properties[key], this._defaults[key]); + } + + if (shouldEmit) { + // Since the value does not match default, we will notify the discrepancy with Cordova's recommended value. + events.emit('info', `[Gradle Properties] Detected Gradle property "${key}" with the value of "${properties[key]}", Cordova's recommended value is "${this._defaults[key]}"`); + } } else { // When the current value exists but does not match the new value or does matches the default key value, the new value it set. events.emit('verbose', `[Gradle Properties] Updating Gradle property "${key}" with the value of "${properties[key]}"`); @@ -103,6 +110,34 @@ class GradlePropertiesParser { }); } + _isJVMMemoryLessThanRecommended (memoryValue, recommendedMemoryValue) { + const UNIT = 2; + const SIZE = 1; + const regex = /-Xmx+([0-9]+)+([mMgGkK])/; + + const recommendedCapture = regex.exec(recommendedMemoryValue); + const recommendedBase = this._getBaseJVMSize(recommendedCapture[SIZE], recommendedCapture[UNIT]); + const memoryCapture = regex.exec(memoryValue); + const memoryBase = this._getBaseJVMSize(memoryCapture[SIZE], memoryCapture[UNIT]); + + return memoryBase < recommendedBase; + } + + _getBaseJVMSize (size, unit) { + const KILOBYTE = 1024; + const MEGABYTE = 1048576; + const GIGABYTE = 1073741824; + + switch (unit.toLowerCase()) { + case 'k': return size * KILOBYTE; + case 'm': return size * MEGABYTE; + case 'g': return size * GIGABYTE; + } + + events.emit('warn', `[Gradle Properties] Unknown memory size unit (${unit})`); + return null; + } + /** * Saves any changes that has been made to the properties file. */ diff --git a/bin/templates/cordova/lib/prepare.js b/bin/templates/cordova/lib/prepare.js index 7ed47047..098cab8c 100644 --- a/bin/templates/cordova/lib/prepare.js +++ b/bin/templates/cordova/lib/prepare.js @@ -19,6 +19,7 @@ var fs = require('fs-extra'); var path = require('path'); +const nopt = require('nopt'); var events = require('cordova-common').events; var AndroidManifest = require('./AndroidManifest'); var checkReqs = require('./check_reqs'); @@ -33,9 +34,21 @@ const utils = require('./utils'); const GradlePropertiesParser = require('./config/GradlePropertiesParser'); +function parseArguments (argv) { + return nopt({ + // `jvmargs` is a valid option however, we don't actually want to parse it because we want the entire string as is. + // jvmargs: String + }, {}, argv || [], 0); +} + module.exports.prepare = function (cordovaProject, options) { var self = this; + let args = {}; + if (options && options.options) { + args = parseArguments(options.options.argv); + } + var platformJson = PlatformJson.load(this.locations.root, this.platform); var munger = new PlatformMunger(this.platform, this.locations.root, platformJson, new PluginInfoProvider()); @@ -53,6 +66,7 @@ module.exports.prepare = function (cordovaProject, options) { if (minSdkVersion) gradlePropertiesUserConfig.cdvMinSdkVersion = minSdkVersion; if (maxSdkVersion) gradlePropertiesUserConfig.cdvMaxSdkVersion = maxSdkVersion; if (targetSdkVersion) gradlePropertiesUserConfig.cdvTargetSdkVersion = targetSdkVersion; + if (args.jvmargs) gradlePropertiesUserConfig['org.gradle.jvmargs'] = args.jvmargs; if (isGradlePluginKotlinEnabled) { gradlePropertiesUserConfig['kotlin.code.style'] = gradlePluginKotlinCodeStyle || 'official'; } diff --git a/spec/unit/config/GradlePropertiesParser.spec.js b/spec/unit/config/GradlePropertiesParser.spec.js index 4c05da2b..3dcc1ff0 100644 --- a/spec/unit/config/GradlePropertiesParser.spec.js +++ b/spec/unit/config/GradlePropertiesParser.spec.js @@ -180,4 +180,64 @@ describe('Gradle Builder', () => { expect(emitSpy.calls.argsFor(0)[1]).toContain('Updating and Saving File'); }); }); + + describe('JVM Settings detection', () => { + const parser = new GradlePropertiesParser('/root'); + + describe('_getBaseJVMSize', () => { + it('1024k = 1048576', () => { + expect(parser._getBaseJVMSize(1024, 'k')).toBe(1048576); + expect(parser._getBaseJVMSize(1024, 'K')).toBe(1048576); + }); + + it('1024m = 1073741824', () => { + expect(parser._getBaseJVMSize(1024, 'm')).toBe(1073741824); + expect(parser._getBaseJVMSize(1024, 'M')).toBe(1073741824); + }); + + it('2g = 2097152', () => { + expect(parser._getBaseJVMSize(2, 'g')).toBe(2147483648); + expect(parser._getBaseJVMSize(2, 'G')).toBe(2147483648); + }); + + it('unknown units should warn', () => { + const emitSpy = jasmine.createSpy('emit'); + GradlePropertiesParser.__set__('events', { + emit: emitSpy + }); + + parser._getBaseJVMSize(1024, 'bad unit'); + expect(emitSpy.calls.argsFor(0)[1]).toContain('Unknown memory size unit'); + }); + }); + + describe('JVM recommended tests', () => { + const recommended = '-Xmx2048m'; + + const tests = { + // kb + '1024k': true, + '2097152k': false, + '2097151k': true, + '2097153k': false, + + // mb + '1024m': true, + '2048m': false, + '2047m': true, + '2049m': false, + + // gb + '1g': true, + '3g': false, + '2g': false + }; + + for (const i in tests) { + it(i + ' should return ' + tests[i], () => { + expect(parser._isJVMMemoryLessThanRecommended('-Xmx' + i, recommended)).toBe(tests[i]); + }); + } + }); + }); }); diff --git a/spec/unit/prepare.spec.js b/spec/unit/prepare.spec.js index da6fcca2..e10c319a 100644 --- a/spec/unit/prepare.spec.js +++ b/spec/unit/prepare.spec.js @@ -20,6 +20,7 @@ var rewire = require('rewire'); var path = require('path'); var CordovaError = require('cordova-common').CordovaError; +const GradlePropertiesParser = require('../../bin/templates/cordova/lib/config/GradlePropertiesParser'); const PATH_RESOURCE = path.join('platforms', 'android', 'app', 'src', 'main', 'res'); @@ -751,3 +752,83 @@ describe('cleanIcons method', function () { expect(actualResourceMap).toEqual(expectedResourceMap); }); }); + +describe('prepare arguments', () => { + // Rewire + let Api; + let api; + let prepare; + + // Spies + let gradlePropertiesParserSpy; + + // Mock Data + let cordovaProject; + let options; + + beforeEach(function () { + Api = rewire('../../bin/templates/cordova/Api'); + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + cordovaProject = { + root: '/mock', + projectConfig: { + path: '/mock/config.xml', + cdvNamespacePrefix: 'cdv' + }, + locations: { + plugins: '/mock/plugins', + www: '/mock/www' + } + }; + + options = { + options: {} + }; + + Api.__set__('ConfigParser', + jasmine.createSpy('ConfigParser') + .and.returnValue(cordovaProject.projectConfig) + ); + + Api.__set__('prepare', prepare.prepare); + + prepare.__set__('events', { + emit: jasmine.createSpy('emit') + }); + prepare.__set__('updateWww', jasmine.createSpy()); + prepare.__set__('updateProjectAccordingTo', jasmine.createSpy('updateProjectAccordingTo') + .and.returnValue(Promise.resolve())); + prepare.__set__('updateIcons', jasmine.createSpy('updateIcons').and.returnValue(Promise.resolve())); + prepare.__set__('updateSplashes', jasmine.createSpy('updateSplashes').and.returnValue(Promise.resolve())); + prepare.__set__('updateFileResources', jasmine.createSpy('updateFileResources').and.returnValue(Promise.resolve())); + prepare.__set__('updateConfigFilesFrom', + jasmine.createSpy('updateConfigFilesFrom') + .and.returnValue({ + getPreference: jasmine.createSpy('getPreference') + })); + + gradlePropertiesParserSpy = spyOn(GradlePropertiesParser.prototype, 'configure'); + + api = new Api(); + }); + + it('runs without arguments', () => { + expectAsync( + api.prepare(cordovaProject, options).then(() => { + expect(gradlePropertiesParserSpy).toHaveBeenCalledWith({}); + }) + ).toBeResolved(); + }); + + it('runs with jvmargs', () => { + options.options.argv = ['--jvmargs=-Xmx=4096m']; + expectAsync( + api.prepare(cordovaProject, options).then(() => { + expect(gradlePropertiesParserSpy).toHaveBeenCalledWith({ + 'org.gradle.jvmargs': '-Xmx=4096m' + }); + }) + ).toBeResolved(); + }); +});