From 68bb9e2780a8902375cf6f6c08d255f8fa51f949 Mon Sep 17 00:00:00 2001 From: Karen Tran Date: Fri, 22 Apr 2016 10:05:12 -0400 Subject: [PATCH 1/3] CB-11023 Add attribute through config-file tag and fix merge issue Add back comment and fix format Allow overwrite attribute --- .../src/ConfigChanges/ConfigChanges.js | 2 +- .../src/ConfigChanges/ConfigFile.js | 14 ++++- cordova-common/src/PluginInfo/PluginInfo.js | 1 + cordova-common/src/util/xml-helpers.js | 51 +++++++++++++++++-- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/cordova-common/src/ConfigChanges/ConfigChanges.js b/cordova-common/src/ConfigChanges/ConfigChanges.js index a914fc80e..212e65565 100644 --- a/cordova-common/src/ConfigChanges/ConfigChanges.js +++ b/cordova-common/src/ConfigChanges/ConfigChanges.js @@ -291,7 +291,7 @@ function generate_plugin_config_munge(pluginInfo, vars) { }); } // 2. add into munge - mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after }); + mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after, attr: change.attr }); }); }); return munge; diff --git a/cordova-common/src/ConfigChanges/ConfigFile.js b/cordova-common/src/ConfigChanges/ConfigFile.js index ad12f386f..5749e0dbd 100644 --- a/cordova-common/src/ConfigChanges/ConfigFile.js +++ b/cordova-common/src/ConfigChanges/ConfigFile.js @@ -103,7 +103,12 @@ ConfigFile.prototype.graft_child = function ConfigFile_graft_child(selector, xml var result; if (self.type === 'xml') { var xml_to_graft = [modules.et.XML(xml_child.xml)]; - result = modules.xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after); + if (xml_child.attr) { + result = modules.xml_helpers.graftXMLAttr(self.data, xml_to_graft, selector, xml_child.after); + } + else { + result = modules.xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after); + } if ( !result) { throw new Error('grafting xml at selector "' + selector + '" from "' + filepath + '" during config install went bad :('); } @@ -123,7 +128,12 @@ ConfigFile.prototype.prune_child = function ConfigFile_prune_child(selector, xml var result; if (self.type === 'xml') { var xml_to_graft = [modules.et.XML(xml_child.xml)]; - result = modules.xml_helpers.pruneXML(self.data, xml_to_graft, selector); + if (xml_child.attr) { + result = modules.xml_helpers.pruneXMLAttr(self.data, xml_to_graft, selector); + } + else { + result = modules.xml_helpers.pruneXML(self.data, xml_to_graft, selector); + } } else { // plist file result = modules.plist_helpers.prunePLIST(self.data, xml_child.xml, selector); diff --git a/cordova-common/src/PluginInfo/PluginInfo.js b/cordova-common/src/PluginInfo/PluginInfo.js index e1a1b3180..6e9c739be 100644 --- a/cordova-common/src/PluginInfo/PluginInfo.js +++ b/cordova-common/src/PluginInfo/PluginInfo.js @@ -138,6 +138,7 @@ function PluginInfo(dirname) { { target : tag.attrib['target'] , parent : tag.attrib['parent'] , after : tag.attrib['after'] + , attr : tag.attrib['attr'] , xmls : tag.getchildren() // To support demuxing via versions , versions : tag.attrib['versions'] diff --git a/cordova-common/src/util/xml-helpers.js b/cordova-common/src/util/xml-helpers.js index 6366af968..aa525820c 100644 --- a/cordova-common/src/util/xml-helpers.js +++ b/cordova-common/src/util/xml-helpers.js @@ -97,6 +97,27 @@ module.exports = { return true; }, + // adds attributes of each node to doc at selector + graftXMLAttr: function(doc, nodes, selector, after) { + var parent = resolveParent(doc, selector); + if (!parent) return false; + + nodes.forEach(function (node) { + var currentNode = parent.find(node.tag); + if (currentNode) { + var attributes = node.attrib; + for (var attribute in attributes) { + currentNode.attrib[attribute] = node.attrib[attribute]; + } + } + else { + return false; + } + }); + + return true; + }, + // removes node from doc at selector pruneXML: function(doc, nodes, selector) { var parent = resolveParent(doc, selector); @@ -114,6 +135,30 @@ module.exports = { return true; }, + // removes attributes of each node from doc at selector + pruneXMLAttr: function(doc, nodes, selector) { + var parent = resolveParent(doc, selector); + if (!parent) return false; + + nodes.forEach(function (node) { + var currentNode = parent.find(node.tag); + if (currentNode) { + var attributes = node.attrib; + for (var attribute in attributes) { + if (currentNode.attrib[attribute] !== undefined && + currentNode.attrib[attribute] === node.attrib[attribute]) { + delete currentNode.attrib[attribute]; + } + } + } + else { + return false; + } + }); + + return true; + }, + parseElementtreeSync: function (filename) { var contents = fs.readFileSync(filename, 'utf-8'); if(contents) { @@ -257,19 +302,19 @@ function mergeXml(src, dest, platform, clobber) { dest.append(destChild); } } - + function removeDuplicatePreferences(xml) { // reduce preference tags to a hashtable to remove dupes var prefHash = xml.findall('preference[@name][@value]').reduce(function(previousValue, currentValue) { previousValue[ currentValue.attrib.name ] = currentValue.attrib.value; return previousValue; }, {}); - + // remove all preferences xml.findall('preference[@name][@value]').forEach(function(pref) { xml.remove(pref); }); - + // write new preferences Object.keys(prefHash).forEach(function(key, index) { var element = et.SubElement(xml, 'preference'); From d10c1a203fa8d9531006099611a8624e959bb63d Mon Sep 17 00:00:00 2001 From: Karen Tran Date: Thu, 28 Apr 2016 11:58:37 -0400 Subject: [PATCH 2/3] Add tests --- .../spec/ConfigChanges/ConfigChanges.spec.js | 42 +++++++++++ .../org.test.xmlattributestest/plugin.xml | 47 ++++++++++++ cordova-common/spec/util/xml-helpers.spec.js | 73 +++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 cordova-common/spec/fixtures/plugins/org.test.xmlattributestest/plugin.xml diff --git a/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js b/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js index df54fe1a9..5cc1a2d7b 100644 --- a/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js +++ b/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js @@ -33,6 +33,7 @@ var configChanges = require('../../src/ConfigChanges/ConfigChanges'), shareddepsplugin = path.join(__dirname, '../fixtures/plugins/org.test.shareddeps'), configplugin = path.join(__dirname, '../fixtures/plugins/org.test.configtest'), varplugin = path.join(__dirname, '../fixtures/plugins/com.adobe.vars'), + attributesplugin = path.join(__dirname, '../fixtures/plugins/org.test.xmlattributestest'), plistplugin = path.join(__dirname, '../fixtures/plugins/org.apache.plist'), android_two_project = path.join(__dirname, '../fixtures/projects/android_two/*'), android_two_no_perms_project = path.join(__dirname, '../fixtures/projects/android_two_no_perms', '*'), @@ -273,6 +274,24 @@ describe('config-changes module', function() { munger.process(plugins_dir); expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'), 'utf-8'); }); + it('should call graftXMLAttr for every new config munge it introduces (every leaf in config munge that does not exist)', function() { + shell.cp('-rf', attributesplugin, plugins_dir); + var platformJson = PlatformJson.load(plugins_dir, 'android'); + platformJson.addInstalledPluginToPrepareQueue('org.test.xmlattributestest', {}); + + var spy = spyOn(xml_helpers, 'graftXMLAttr').andReturn(true); + + var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider); + munger.process(plugins_dir); + expect(spy.calls.length).toEqual(7); + expect(spy.argsForCall[0][2]).toEqual('application'); + expect(spy.argsForCall[1][2]).toEqual('application'); + expect(spy.argsForCall[2][2]).toEqual('/manifest'); + expect(spy.argsForCall[3][2]).toEqual('/manifest'); + expect(spy.argsForCall[4][2]).toEqual('/*/application'); + expect(spy.argsForCall[5][2]).toEqual('/*/application'); + expect(spy.argsForCall[6][2]).toEqual('/manifest/application'); + }); }); describe('of plist config files', function() { it('should write empty string nodes with no whitespace', function() { @@ -411,6 +430,29 @@ describe('config-changes module', function() { expect(platformJson.root.prepare_queue.uninstalled.length).toEqual(0); expect(platformJson.root.installed_plugins['com.adobe.vars']).not.toBeDefined(); }); + it('should call pruneXMLAttr for every config munge it completely removes from the app (every leaf that is decremented to 0)', function() { + shell.cp('-rf', android_two_project, temp); + shell.cp('-rf', attributesplugin, plugins_dir); + + // Run through an "install" + var platformJson = PlatformJson.load(plugins_dir, 'android'); + platformJson.addInstalledPluginToPrepareQueue('org.test.xmlattributestest', {}); + var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider); + munger.process(plugins_dir); + + // Now set up an uninstall and make sure pruneXMLAttr is called properly + platformJson.addUninstalledPluginToPrepareQueue('org.test.xmlattributestest'); + var spy = spyOn(xml_helpers, 'pruneXMLAttr').andReturn(true); + munger.process(plugins_dir); + expect(spy.calls.length).toEqual(7); + expect(spy.argsForCall[0][2]).toEqual('application'); + expect(spy.argsForCall[1][2]).toEqual('application'); + expect(spy.argsForCall[2][2]).toEqual('/manifest'); + expect(spy.argsForCall[3][2]).toEqual('/manifest'); + expect(spy.argsForCall[4][2]).toEqual('/*/application'); + expect(spy.argsForCall[5][2]).toEqual('/*/application'); + expect(spy.argsForCall[6][2]).toEqual('/manifest/application'); + }); }); }); }); diff --git a/cordova-common/spec/fixtures/plugins/org.test.xmlattributestest/plugin.xml b/cordova-common/spec/fixtures/plugins/org.test.xmlattributestest/plugin.xml new file mode 100644 index 000000000..3e8305456 --- /dev/null +++ b/cordova-common/spec/fixtures/plugins/org.test.xmlattributestest/plugin.xml @@ -0,0 +1,47 @@ + + + + + + Adding attributes through config-file + + + + + + + + + + + + + + + + + + + + + diff --git a/cordova-common/spec/util/xml-helpers.spec.js b/cordova-common/spec/util/xml-helpers.spec.js index 91128a6fa..060b9e171 100644 --- a/cordova-common/spec/util/xml-helpers.spec.js +++ b/cordova-common/spec/util/xml-helpers.spec.js @@ -128,6 +128,41 @@ describe('xml-helpers', function(){ expect(config_xml.find('plugins').getchildren().length).toEqual(0); }); }); + describe('pruneXMLAttr', function() { + var android_manifest_xml; + + beforeEach(function() { + android_manifest_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '../fixtures/projects/android/AndroidManifest.xml')); + }); + + it('should remove attribute from the specified selector', function() { + var children = [et.XML('')]; + xml_helpers.pruneXMLAttr(android_manifest_xml, children, 'application'); + var activityAttr = android_manifest_xml.find('application/activity').attrib; + expect(Object.keys(activityAttr).length).toEqual(2); + expect(activityAttr['android:configChanges']).not.toBeDefined(); + }); + it('should remove attribute from absolute selector', function() { + var children = [et.XML('')]; + xml_helpers.pruneXMLAttr(android_manifest_xml, children, '/manifest'); + var applicationAttr = android_manifest_xml.find('application').attrib; + expect(Object.keys(applicationAttr).length).toEqual(2); + expect(applicationAttr['android:debuggable']).not.toBeDefined(); + }); + it('should remove attribute from absolute selectors with wildcards', function() { + var children = [et.XML('')]; + xml_helpers.pruneXMLAttr(android_manifest_xml, children, '/*/application'); + var activityAttr = android_manifest_xml.find('application/activity').attrib; + expect(Object.keys(activityAttr).length).toEqual(2); + expect(activityAttr['android:configChanges']).not.toBeDefined(); + }); + it('should do nothing if the attribute cannot be found', function() { + var children = [et.XML('')]; + xml_helpers.pruneXMLAttr(android_manifest_xml, children, '/manifest'); + var applicationAttr = android_manifest_xml.find('application').attrib; + expect(Object.keys(applicationAttr).length).toEqual(3); + }); + }); describe('graftXML', function() { var config_xml, plugin_xml; @@ -153,6 +188,44 @@ describe('xml-helpers', function(){ expect(config_xml.findall('access').length).toEqual(3); }); }); + describe('graftXMLAttr', function() { + var android_manifest_xml, plugin_xml; + + beforeEach(function() { + android_manifest_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '../fixtures/projects/android/AndroidManifest.xml')); + plugin_xml = xml_helpers.parseElementtreeSync(path.join(__dirname, '../fixtures/plugins/org.test.xmlattributestest/plugin.xml')); + }); + + it('should add or overwrite attributes to specified selector', function() { + var children = plugin_xml.findall('platform/config-file')[0].getchildren(); + xml_helpers.graftXMLAttr(android_manifest_xml, children, 'application'); + var activityAttr = android_manifest_xml.find('application/activity').attrib; + expect(Object.keys(activityAttr).length).toEqual(4); + expect(activityAttr['android:hardwareAccelerated']).toEqual('true'); + expect(activityAttr['android:configChanges']).toEqual('orientation'); + }); + it('should add or overwrite attributes to absolute selector', function() { + var children = plugin_xml.findall('platform/config-file')[1].getchildren(); + xml_helpers.graftXMLAttr(android_manifest_xml, children, '/manifest'); + var usesSdkAttr = android_manifest_xml.find('uses-sdk').attrib; + expect(Object.keys(usesSdkAttr).length).toEqual(2); + expect(usesSdkAttr['android:targetSdkVersion']).toEqual('23'); + expect(usesSdkAttr['android:minSdkVersion']).toEqual('14'); + }); + it('should add or overwrite attributes to absolute selector with wildcards', function() { + var children = plugin_xml.findall('platform/config-file')[2].getchildren(); + xml_helpers.graftXMLAttr(android_manifest_xml, children, '/*/application'); + var activityAttr = android_manifest_xml.find('application/activity').attrib; + expect(Object.keys(activityAttr).length).toEqual(4); + expect(activityAttr['android:hardwareAccelerated']).toEqual('true'); + expect(activityAttr['android:configChanges']).toEqual('orientation'); + }); + it('should do nothing if the children cannot be found', function() { + var children = plugin_xml.findall('platform/config-file')[3].getchildren(); + xml_helpers.graftXMLAttr(android_manifest_xml, children, '/manifest/application'); + expect(android_manifest_xml.find('application/poop')).toBe(null); + }); + }); describe('mergeXml', function () { var dstXml; From be79faa84dcb9b9dbe66959403dd6efc548ad881 Mon Sep 17 00:00:00 2001 From: Karen Tran Date: Mon, 9 May 2016 15:02:56 -0400 Subject: [PATCH 3/3] CB-11023 Add config-file functionality to config.xml and fix merges --- .../spec/ConfigChanges/ConfigChanges.spec.js | 28 +++++++++++- .../spec/ConfigParser/ConfigParser.spec.js | 4 ++ cordova-common/spec/fixtures/test-config.xml | 9 ++++ .../src/ConfigChanges/ConfigChanges.js | 43 +++++++++++++++++++ .../src/ConfigParser/ConfigParser.js | 24 ++++++++++- cordova-lib/src/cordova/prepare.js | 8 ++++ 6 files changed, 114 insertions(+), 2 deletions(-) diff --git a/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js b/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js index 5cc1a2d7b..e04b11b59 100644 --- a/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js +++ b/cordova-common/spec/ConfigChanges/ConfigChanges.spec.js @@ -39,11 +39,13 @@ var configChanges = require('../../src/ConfigChanges/ConfigChanges'), android_two_no_perms_project = path.join(__dirname, '../fixtures/projects/android_two_no_perms', '*'), ios_config_xml = path.join(__dirname, '../fixtures/projects/ios-config-xml/*'), windows_testapp_jsproj = path.join(__dirname, '../fixtures/projects/windows/TestApp.jsproj'), - plugins_dir = path.join(temp, 'cordova', 'plugins'); + plugins_dir = path.join(temp, 'cordova', 'plugins'), + test_config_xml = path.join(__dirname, '../fixtures/test-config.xml'); var mungeutil = require('../../src/ConfigChanges/munge-util'); var PlatformJson = require('../../src/PlatformJson'); var PluginInfoProvider = require('../../src/PluginInfo/PluginInfoProvider'); var PluginInfo = require('../../src/PluginInfo/PluginInfo'); +var ConfigParser = require('../../src/ConfigParser/ConfigParser'); // TODO: dont do fs so much @@ -124,6 +126,30 @@ describe('config-changes module', function() { }); }); + describe('add_config_changes method', function() { + it('should handle config-file tag from config.xml', function() { + shell.cp('-rf', android_two_project, temp); + + var filepath = path.join(plugins_dir, 'android.json'); + var platformJson = new PlatformJson(filepath, 'android'); + var munger = new configChanges.PlatformMunger('android', temp, platformJson); + var config = new ConfigParser(test_config_xml); + munger.add_config_changes(config, true).save_all(); + + var manifest = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8'))); + var application = manifest.find('./application'); + var uses_sdk = manifest.find('./uses-sdk'); + var activity = manifest.find('./application/activity[@android:name="NewActivity"]'); + + expect(application).not.toBe(null); + expect(application.attrib['android:name']).toEqual('MyApplication'); + expect(uses_sdk).not.toBe(null); + expect(uses_sdk.attrib['android:minSdkVersion']).toEqual('15'); + expect(activity).not.toBe(null); + expect(activity.attrib['android:name']).toEqual('NewActivity'); + }); + }); + describe('generate_plugin_config_munge method', function() { describe('for android projects', function() { beforeEach(function() { diff --git a/cordova-common/spec/ConfigParser/ConfigParser.spec.js b/cordova-common/spec/ConfigParser/ConfigParser.spec.js index 097fe617d..c49106be9 100644 --- a/cordova-common/spec/ConfigParser/ConfigParser.spec.js +++ b/cordova-common/spec/ConfigParser/ConfigParser.spec.js @@ -227,6 +227,10 @@ describe('config.xml parser', function () { var navigations = cfg.getAllowNavigations(); expect(navigations.length).not.toEqual(0); }); + it('it should read tag entries', function(){ + var configFile = cfg.getConfigFiles('android'); + expect(configFile.length).not.toEqual(0); + }); }); describe('static resources', function() { var hasPlatformPropertyDefined = function (e) { return !!e.platform; }; diff --git a/cordova-common/spec/fixtures/test-config.xml b/cordova-common/spec/fixtures/test-config.xml index df479484a..ebbf4d02a 100644 --- a/cordova-common/spec/fixtures/test-config.xml +++ b/cordova-common/spec/fixtures/test-config.xml @@ -85,6 +85,15 @@ + + + + + + + + + diff --git a/cordova-common/src/ConfigChanges/ConfigChanges.js b/cordova-common/src/ConfigChanges/ConfigChanges.js index 212e65565..0345a321d 100644 --- a/cordova-common/src/ConfigChanges/ConfigChanges.js +++ b/cordova-common/src/ConfigChanges/ConfigChanges.js @@ -166,6 +166,49 @@ function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increm } +// Handle config-file changes from config.xml +PlatformMunger.prototype.add_config_changes = add_config_changes; +function add_config_changes(config, should_increment) { + var self = this; + var platform_config = self.platformJson.root; + + // get config munge + var config_munge = self.generate_plugin_config_munge(config, null); + // global munge looks at all changes to config files + + // TODO: The should_increment param is only used by cordova-cli and is going away soon. + // If should_increment is set to false, avoid modifying the global_munge (use clone) + // and apply the entire config_munge because it's already a proper subset of the global_munge. + var munge, global_munge; + if (should_increment) { + global_munge = platform_config.config_munge; + munge = mungeutil.increment_munge(global_munge, config_munge); + } else { + global_munge = mungeutil.clone_munge(platform_config.config_munge); + munge = config_munge; + } + + for (var file in munge.files) { + // CB-6976 Windows Universal Apps. Compatibility fix for existing plugins. + if (self.platform == 'windows' && file == 'package.appxmanifest' && + !fs.existsSync(path.join(self.project_dir, 'package.appxmanifest'))) { + var substs = ['package.phone.appxmanifest', 'package.windows.appxmanifest', 'package.windows10.appxmanifest']; + /* jshint loopfunc:true */ + substs.forEach(function(subst) { + events.emit('verbose', 'Applying munge to ' + subst); + self.apply_file_munge(subst, munge.files[file]); + }); + /* jshint loopfunc:false */ + } + + self.apply_file_munge(file, munge.files[file]); + } + + // Move to installed/dependent_plugins + return self; +} + + // Load the global munge from platform json and apply all of it. // Used by cordova prepare to re-generate some config file from platform // defaults and the global munge. diff --git a/cordova-common/src/ConfigParser/ConfigParser.js b/cordova-common/src/ConfigParser/ConfigParser.js index aae59dbff..5d751135e 100644 --- a/cordova-common/src/ConfigParser/ConfigParser.js +++ b/cordova-common/src/ConfigParser/ConfigParser.js @@ -100,7 +100,7 @@ ConfigParser.prototype = { return this.doc.getroot().attrib['android-packageName']; }, android_activityName: function() { - return this.doc.getroot().attrib['android-activityName']; + return this.doc.getroot().attrib['android-activityName']; }, ios_CFBundleIdentifier: function() { return this.doc.getroot().attrib['ios-CFBundleIdentifier']; @@ -464,6 +464,28 @@ ConfigParser.prototype = { }; }); }, + /* Get all config-file tags */ + getConfigFiles: function(platform) { + var platform_tag = this.doc.find('./platform[@name="' + platform + '"]'); + var platform_config_files = platform_tag ? platform_tag.findall('config-file') : []; + + var config_files = this.doc.findall('config-file').concat(platform_config_files); + + return config_files.map(function(tag) { + var configFile = + { + target : tag.attrib['target'], + parent : tag.attrib['parent'], + after : tag.attrib['after'], + attr : tag.attrib['attr'], + xmls : tag.getchildren(), + // To support demuxing via versions + versions : tag.attrib['versions'], + deviceTarget: tag.attrib['device-target'] + }; + return configFile; + }); + }, write:function() { fs.writeFileSync(this.path, this.doc.write({indent: 4}), 'utf-8'); } diff --git a/cordova-lib/src/cordova/prepare.js b/cordova-lib/src/cordova/prepare.js index dd82d52b5..097e5f704 100644 --- a/cordova-lib/src/cordova/prepare.js +++ b/cordova-lib/src/cordova/prepare.js @@ -21,6 +21,7 @@ var cordova_util = require('./util'), ConfigParser = require('cordova-common').ConfigParser, PlatformJson = require('cordova-common').PlatformJson, PluginInfoProvider = require('cordova-common').PluginInfoProvider, + PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger, events = require('cordova-common').events, platforms = require('../platforms/platforms'), PlatformApiPoly = require('../platforms/PlatformApiPoly'), @@ -116,6 +117,13 @@ function preparePlatforms (platformList, projectRoot, options) { var browserify = require('../plugman/browserify'); return browserify(project, platformApi); } + }) + .then(function () { + // Handle config-file in config.xml + var platformRoot = path.join(projectRoot, 'platforms', platform); + var platformJson = PlatformJson.load(platformRoot, platform); + var munger = new PlatformMunger(platform, platformRoot, platformJson); + munger.add_config_changes(project.projectConfig, /*should_increment=*/true).save_all(); }); }); }));