CB-14108: fix incorrect count in config_munge in ios.json and android.json
authorKen Naito <fine14r3@gmail.com>
Tue, 11 Sep 2018 17:07:32 +0000 (02:07 +0900)
committerDarryl Pogue <dvpdiner2@gmail.com>
Tue, 11 Sep 2018 17:07:32 +0000 (10:07 -0700)
* Fix to cordova prepare increment count at config_munge in platformJson
* Fix to cordova prepare when remove config-file case

spec/ConfigChanges/ConfigChanges.spec.js
spec/CordovaError/CordovaError.spec.js [new file with mode: 0644]
spec/fixtures/test-configfile0.xml [new file with mode: 0644]
spec/fixtures/test-configfile1.xml [new file with mode: 0644]
spec/fixtures/test-configfile2.xml [new file with mode: 0644]
src/ConfigChanges/ConfigChanges.js
src/ConfigChanges/ConfigFile.js
src/ConfigChanges/munge-util.js
src/ConfigParser/ConfigParser.js

index fe83c70..7f792d2 100644 (file)
@@ -46,6 +46,9 @@ var ConfigParser = require('../../src/ConfigParser/ConfigParser');
 var xml = path.join(__dirname, '../fixtures/test-config.xml');
 var editconfig_xml = path.join(__dirname, '../fixtures/test-editconfig.xml');
 var configfile_xml = path.join(__dirname, '../fixtures/test-configfile.xml');
+var configfile0_xml = path.join(__dirname, '../fixtures/test-configfile0.xml');
+var configfile1_xml = path.join(__dirname, '../fixtures/test-configfile1.xml');
+var configfile2_xml = path.join(__dirname, '../fixtures/test-configfile2.xml');
 var cfg = new ConfigParser(xml);
 
 // TODO: dont do fs so much
@@ -385,6 +388,32 @@ describe('config-changes module', function () {
                     expect(sdk.attrib['android:minSdkVersion']).toEqual('5');
                     expect(sdk.attrib['android:maxSdkVersion']).toBeUndefined();
                 });
+                it('should recover AndroidManifest after removing editconfig', function () {
+                    var editconfig_cfg = new ConfigParser(editconfig_xml);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+
+                    // once add editconfig
+                    munger.add_config_changes(cfg, true).save_all();
+                    munger.add_config_changes(editconfig_cfg, true).save_all();
+
+                    var am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    var sdk = am_xml.find('./uses-sdk');
+                    expect(sdk).toBeDefined();
+                    expect(sdk.attrib['android:targetSdkVersion']).toEqual('23');
+                    expect(sdk.attrib['android:minSdkVersion']).toEqual('5');
+                    expect(sdk.attrib['android:maxSdkVersion']).toBeUndefined();
+
+                    // should recover
+                    munger.add_config_changes(cfg, true).save_all();
+                    am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    sdk = am_xml.find('./uses-sdk');
+                    expect(sdk).toBeDefined();
+                    expect(sdk.attrib['android:targetSdkVersion']).toEqual('24');
+                    expect(sdk.attrib['android:minSdkVersion']).toEqual('14');
+                    expect(sdk.attrib['android:maxSdkVersion']).toBeUndefined();
+
+                });
                 it('should append new children to XML document tree', function () {
                     var configfile_cfg = new ConfigParser(configfile_xml);
                     var platformJson = PlatformJson.load(plugins_dir, 'android');
@@ -404,6 +433,47 @@ describe('config-changes module', function () {
                     var am_file = fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8');
                     expect(am_file.indexOf('android:name="zoo"')).toBeLessThan(am_file.indexOf('android:name="com.foo.Bar"'));
                 });
+                // testing removing <config-file> tag in config.xml
+                it('should recover AndroidManifest after removing config-file tag', function () {
+                    // add config-file same as previous
+                    var configfile_cfg = new ConfigParser(configfile_xml);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                    munger.add_config_changes(configfile_cfg, true).save_all();
+                    var am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    var activity = am_xml.find('./application/activity[@android:name="com.foo.Bar"]');
+                    expect(activity).not.toBeNull();
+                    // add removing config-file
+                    var configfile0_cfg = new ConfigParser(configfile0_xml); // removing config-file tag
+                    munger.add_config_changes(configfile0_cfg, true).save_all();
+                    am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    activity = am_xml.find('./application/activity[@android:name="com.foo.Bar"]');
+                    expect(activity).toBeNull();
+                });
+                it('should recover AndroidManifest if one of permission tags is removed', function () {
+                    fs.copySync(android_two_no_perms_project, temp);
+                    var configfile2_cfg = new ConfigParser(configfile2_xml);
+                    var platformJson = PlatformJson.load(plugins_dir, 'android');
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                    munger.add_config_changes(configfile2_cfg, true).save_all();
+                    var am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    var permission_vibrate = am_xml.find('./uses-permission[@android:name="android.permission.VIBRATE"]');
+                    var permission_write = am_xml.find('./uses-permission[@android:name="android.permission.WRITE_EXTERNAL_STORAGE"]');
+                    var permission_contacts = am_xml.find('./uses-permission[@android:name="android.permission.READ_CONTACTS"]');
+                    expect(permission_vibrate).not.toBeNull();
+                    expect(permission_write).not.toBeNull();
+                    expect(permission_contacts).toBeNull();
+                    // add removing of of permission tag
+                    var configfile1_cfg = new ConfigParser(configfile1_xml); // removing one of permission tag
+                    munger.add_config_changes(configfile1_cfg, true).save_all();
+                    am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
+                    permission_vibrate = am_xml.find('./uses-permission[@android:name="android.permission.VIBRATE"]');
+                    permission_write = am_xml.find('./uses-permission[@android:name="android.permission.WRITE_EXTERNAL_STORAGE"]');
+                    permission_contacts = am_xml.find('./uses-permission[@android:name="android.permission.READ_CONTACTS"]');
+                    expect(permission_vibrate).not.toBeNull();
+                    expect(permission_write).toBeNull();
+                    expect(permission_contacts).toBeNull();
+                });
                 it('should throw error for conflicting plugin config munge with config.xml config munge', function () {
                     install_plugin(editconfigplugin_two);
 
@@ -437,6 +507,24 @@ describe('config-changes module', function () {
                     expect(fs.readFileSync(path.join(temp, 'SampleApp', 'SampleApp-Info.plist'), 'utf-8')).toMatch(/<string>schema-b<\/string>/);
                     expect(fs.readFileSync(path.join(temp, 'SampleApp', 'SampleApp-Info.plist'), 'utf-8')).not.toMatch(/(<string>schema-a<\/string>[^]*){2,}/);
                 });
+                it('should recover Info.plist after removing config-file tag', function () {
+                    fs.copySync(ios_config_xml, temp);
+                    var configfile2_cfg = new ConfigParser(configfile2_xml);
+                    var platformJson = PlatformJson.load(plugins_dir, 'ios');
+                    var munger = new configChanges.PlatformMunger('ios', temp, platformJson, pluginInfoProvider);
+                    munger.add_config_changes(configfile2_cfg, true).save_all();
+                    var info_plist = fs.readFileSync(path.join(temp, 'SampleApp', 'SampleApp-Info.plist'), 'utf-8');
+                    expect(info_plist).toMatch(/<key>NSCameraUsageDescription<\/key>\s*<string>Please permit Camera<\/string>/);
+                    expect(info_plist).toMatch(/<key>NSPhotoLibraryUsageDescription<\/key>\s*<string>Please permit PhotoLibrary<\/string>/);
+                    expect(info_plist).toMatch(/<key>LSApplicationQueriesSchemes<\/key>\s*<array>\s*<string>twitter<\/string>\s*<string>fb<\/string>\s*<\/array>/);
+                    var configfile1_cfg = new ConfigParser(configfile1_xml);
+                    munger.add_config_changes(configfile1_cfg, true).save_all();
+                    info_plist = fs.readFileSync(path.join(temp, 'SampleApp', 'SampleApp-Info.plist'), 'utf-8');
+                    expect(info_plist).toMatch(/<key>NSCameraUsageDescription<\/key>\s*<string>This app uses Camera<\/string>/);
+                    expect(info_plist).not.toMatch(/<key>NSPhotoLibraryUsageDescription<\/key>\s*<string>Please permit PhotoLibrary<\/string>/);
+                    expect(info_plist).not.toMatch(/<key>LSApplicationQueriesSchemes<\/key>\s*<array>\s*<string>twitter<\/string>\s*<string>fb<\/string>\s*<\/array>/);
+                    expect(info_plist).toMatch(/<key>LSApplicationQueriesSchemes<\/key>\s*<array>\s*<string>twitter<\/string>\s*<\/array>/);
+                });
             });
             describe('of binary plist config files', function () {
                 it('should merge dictionaries and arrays, removing duplicates', function () {
diff --git a/spec/CordovaError/CordovaError.spec.js b/spec/CordovaError/CordovaError.spec.js
new file mode 100644 (file)
index 0000000..56e3e24
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+    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.
+*/
+
+var CordovaError = require('../../src/CordovaError/CordovaError');
+
+describe('CordovaError class', function () {
+    it('Test 001 : should be constructable', function () {
+        expect(new CordovaError('error')).toEqual(jasmine.any(CordovaError));
+    });
+
+    it('Test 002 : getErrorCodeName works', function () {
+        var error002_1 = new CordovaError('error', 0);
+        expect(error002_1.getErrorCodeName()).toEqual('UNKNOWN_ERROR');
+        var error002_2 = new CordovaError('error', 1);
+        expect(error002_2.getErrorCodeName()).toEqual('EXTERNAL_TOOL_ERROR');
+    });
+
+    it('Test 003 : toString works', function () {
+        var error003_1 = new CordovaError('error', 0);
+        expect(error003_1.toString(false)).toEqual('error');
+        expect(error003_1.toString(true).substring(0, 12)).toEqual('CordovaError');
+        var error003_2 = new CordovaError('error', 1);
+        expect(error003_2.toString(false)).toEqual('External tool failed with an error: error');
+    });
+});
diff --git a/spec/fixtures/test-configfile0.xml b/spec/fixtures/test-configfile0.xml
new file mode 100644 (file)
index 0000000..680c18c
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget android-packageName="io.cordova.hellocordova.android" id="io.cordova.hellocordova" ios-CFBundleIdentifier="io.cordova.hellocordova.ios" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
+    <name>Hello Cordova</name>
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+    <author email="dev@cordova.apache.org" href="http://cordova.io">
+        Apache Cordova Team
+    </author>
+    <content src="index.html" />
+    <access origin="*" />
+    <platform name="android">
+    </platform>
+</widget>
diff --git a/spec/fixtures/test-configfile1.xml b/spec/fixtures/test-configfile1.xml
new file mode 100644 (file)
index 0000000..8c7dee2
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget android-packageName="io.cordova.hellocordova.android" id="io.cordova.hellocordova" ios-CFBundleIdentifier="io.cordova.hellocordova.ios" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
+    <name>Hello Cordova</name>
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+    <author email="dev@cordova.apache.org" href="http://cordova.io">
+        Apache Cordova Team
+    </author>
+    <content src="index.html" />
+    <access origin="*" />
+    <platform name="android">
+      <config-file parent="/manifest" target="AndroidManifest.xml">
+        <uses-permission android:name="android.permission.VIBRATE" />
+      </config-file>
+    </platform>
+    <platform name="ios">
+        <allow-intent href="itms:*" />
+        <allow-intent href="itms-apps:*" />
+        <config-file parent="NSCameraUsageDescription" target="*-Info.plist">
+            <string>This app uses Camera</string>
+        </config-file>
+        <config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist">
+            <array>
+                <string>twitter</string>
+            </array>
+        </config-file>
+    </platform>
+</widget>
diff --git a/spec/fixtures/test-configfile2.xml b/spec/fixtures/test-configfile2.xml
new file mode 100644 (file)
index 0000000..86e503d
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget android-packageName="io.cordova.hellocordova.android" id="io.cordova.hellocordova" ios-CFBundleIdentifier="io.cordova.hellocordova.ios" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
+    <name>Hello Cordova</name>
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+    <author email="dev@cordova.apache.org" href="http://cordova.io">
+        Apache Cordova Team
+    </author>
+    <content src="index.html" />
+    <access origin="*" />
+    <platform name="android">
+      <config-file parent="/manifest" target="AndroidManifest.xml">
+        <uses-permission android:name="android.permission.VIBRATE" />
+        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+      </config-file>
+    </platform>
+    <platform name="ios">
+        <allow-intent href="itms:*" />
+        <allow-intent href="itms-apps:*" />
+        <config-file parent="NSCameraUsageDescription" target="*-Info.plist">
+            <string>Please permit Camera</string>
+        </config-file>
+        <config-file target="*-Info.plist" parent="NSPhotoLibraryUsageDescription">
+            <string>Please permit PhotoLibrary</string>
+        </config-file>
+        <config-file parent="LSApplicationQueriesSchemes" target="*-Info.plist">
+            <array>
+                <string>twitter</string>
+                <string>fb</string>
+            </array>
+        </config-file>
+    </platform>
+</widget>
index e7ad0e2..87107e5 100644 (file)
@@ -169,7 +169,6 @@ function add_config_changes (config, should_increment) {
     var self = this;
     var platform_config = self.platformJson.root;
 
-    var config_munge;
     var changes = [];
 
     if (config.getEditConfigs) {
@@ -186,33 +185,48 @@ function add_config_changes (config, should_increment) {
         }
     }
 
-    if (changes && changes.length > 0) {
-        var isConflictingInfo = is_conflicting(changes, platform_config.config_munge, self, true /* always force overwrite other edit-config */);
-        if (isConflictingInfo.conflictFound) {
+    var platform_config_munge = platform_config.config_munge;
+
+    var differs = (function (changes) {
+        if (changes && changes.length >= 0) {
+            var isConflictingInfo = is_conflicting(changes, platform_config_munge, self, true /* always force overwrite other edit-config */);
             var conflict_munge;
             var conflict_file;
-
-            if (Object.keys(isConflictingInfo.configxmlMunge.files).length !== 0) {
-                // silently remove conflicting config.xml munges so new munges can be added
-                conflict_munge = mungeutil.decrement_munge(platform_config.config_munge, isConflictingInfo.configxmlMunge);
+            // remove unused config munge.
+            if (Object.keys(isConflictingInfo.unusedConfigMunge.files).length !== 0) {
+                conflict_munge = mungeutil.decrement_munge(platform_config_munge, isConflictingInfo.unusedConfigMunge);
                 for (conflict_file in conflict_munge.files) {
                     self.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
                 }
             }
-            if (Object.keys(isConflictingInfo.conflictingMunge.files).length !== 0) {
-                events.emit('warn', 'Conflict found, edit-config changes from config.xml will overwrite plugin.xml changes');
+            // check override conflicting munge.
+            if (isConflictingInfo.conflictFound) {
+                if (Object.keys(isConflictingInfo.configxmlMunge.files).length !== 0) {
+                    // silently remove conflicting config.xml munges so new munges can be added
+                    conflict_munge = mungeutil.decrement_munge(platform_config_munge, isConflictingInfo.configxmlMunge);
+                    for (conflict_file in conflict_munge.files) {
+                        self.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
+                    }
+                }
+                if (Object.keys(isConflictingInfo.conflictingMunge.files).length !== 0) {
+                    events.emit('warn', 'Conflict found, edit-config changes from config.xml will overwrite plugin.xml changes');
 
-                // remove conflicting plugin.xml munges
-                conflict_munge = mungeutil.decrement_munge(platform_config.config_munge, isConflictingInfo.conflictingMunge);
-                for (conflict_file in conflict_munge.files) {
-                    self.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
+                    // remove conflicting plugin.xml munges
+                    conflict_munge = mungeutil.decrement_munge(platform_config_munge, isConflictingInfo.conflictingMunge);
+                    for (conflict_file in conflict_munge.files) {
+                        self.apply_file_munge(conflict_file, conflict_munge.files[conflict_file], /* remove = */ true);
+                    }
                 }
+                // skip no chnages
+                return changes.filter(function (x) { return isConflictingInfo.noChanges.indexOf(x) === -1; });
             }
+
         }
-    }
+        return changes;
+    })(changes);
 
     // Add config.xml edit-config and config-file munges
-    config_munge = self.generate_config_xml_munge(config, changes, 'config.xml');
+    var config_munge = self.generate_config_xml_munge(config, differs, 'config.xml');
     self = munge_helper(should_increment, self, platform_config, config_munge);
 
     // Move to installed/dependent_plugins
@@ -257,7 +271,7 @@ function reapply_global_munge () {
     return self;
 }
 
-// generate_plugin_config_munge
+// generate_config_xml_munge
 // Generate the munge object from config.xml
 PlatformMunger.prototype.generate_config_xml_munge = generate_config_xml_munge;
 function generate_config_xml_munge (config, config_changes, type) {
@@ -282,7 +296,7 @@ function generate_config_xml_munge (config, config_changes, type) {
             if (change.mode) {
                 mungeutil.deep_add(munge, change.file, change.target, { xml: stringified, count: 1, mode: change.mode, id: id });
             } else {
-                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, id: id });
             }
         });
     });
@@ -335,43 +349,80 @@ function is_conflicting (editchanges, config_munge, self, force) {
     var configxmlMunge = { files: {} };
     var conflictingParent;
     var conflictingPlugin;
+    var noChanges = [];
+
+    var unusedConfigMunge = mungeutil.filterClone(config_munge, function (x) { return x.id === 'config.xml'; });
 
     editchanges.forEach(function (editchange) {
-        if (files[editchange.file]) {
-            var parents = files[editchange.file].parents;
-            var target = parents[editchange.target];
+        var change_file, change_target, change_id;
+        if (editchange.file) { // for edit_config
+            change_file = editchange.file;
+            change_target = editchange.target;
+            change_id = editchange.id;
+        } else { // for config_file
+            change_file = editchange.target;
+            change_target = editchange.parent;
+            change_id = editchange.id;
+        }
+
+        if (files[change_file]) {
+            var parents = files[change_file].parents;
+            var target = parents[change_target];
 
             // Check if the edit target will resolve to an existing target
             if (!target || target.length === 0) {
-                var file_xml = self.config_keeper.get(self.project_dir, self.platform, editchange.file).data;
-                var resolveEditTarget = xml_helpers.resolveParent(file_xml, editchange.target);
-                var resolveTarget;
-
-                if (resolveEditTarget) {
-                    for (var parent in parents) {
-                        resolveTarget = xml_helpers.resolveParent(file_xml, parent);
-                        if (resolveEditTarget === resolveTarget) {
-                            conflictingParent = parent;
-                            target = parents[parent];
-                            break;
+                var configFile = self.config_keeper.get(self.project_dir, self.platform, change_file);
+                var file_type = configFile.type;
+                if (file_type === 'xml') {
+                    var file_xml = configFile.data;
+                    var resolveEditTarget = xml_helpers.resolveParent(file_xml, change_target);
+                    var resolveTarget;
+
+                    if (resolveEditTarget) {
+                        for (var parent in parents) {
+                            resolveTarget = xml_helpers.resolveParent(file_xml, parent);
+                            if (resolveEditTarget === resolveTarget) {
+                                conflictingParent = parent;
+                                target = parents[parent];
+                                break;
+                            }
                         }
                     }
+                } else {
+                    conflictingParent = change_target;
                 }
             } else {
-                conflictingParent = editchange.target;
+                conflictingParent = change_target;
             }
 
             if (target && target.length !== 0) {
                 // conflict has been found
                 conflictFound = true;
 
-                if (editchange.id === 'config.xml') {
+                if (change_id === 'config.xml') {
+                    target.forEach(function (target_elem) {
+                        mungeutil.deep_remove(unusedConfigMunge, change_file, conflictingParent, target_elem);
+                    });
+                }
+
+                var xmlStrList, isSameAll;
+                if (change_id === 'config.xml') {
                     if (target[0].id === 'config.xml') {
-                        // Keep track of config.xml/config.xml edit-config conflicts
-                        mungeutil.deep_add(configxmlMunge, editchange.file, conflictingParent, target[0]);
+                        xmlStrList = changedXmlStrList(editchange);
+                        isSameAll = compareChangedXmlsAndTarget(xmlStrList, target);
+                        if (isSameAll) {
+                            noChanges.push(editchange);
+                        } else {
+                            // Keep track of config.xml/config.xml edit-config conflicts
+                            target.forEach(function (target_elem) {
+                                mungeutil.deep_add(configxmlMunge, change_file, conflictingParent, target_elem);
+                            });
+                        }
                     } else {
                         // Keep track of config.xml x plugin.xml edit-config conflicts
-                        mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
+                        target.forEach(function (target_elem) {
+                            mungeutil.deep_add(conflictingMunge, change_file, conflictingParent, target_elem);
+                        });
                     }
                 } else {
                     if (target[0].id === 'config.xml') {
@@ -381,8 +432,18 @@ function is_conflicting (editchanges, config_munge, self, force) {
                     }
 
                     if (force) {
-                        // Need to find all conflicts when --force is used, track conflicting munges
-                        mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
+                        xmlStrList = changedXmlStrList(editchange);
+                        isSameAll = compareChangedXmlsAndTarget(xmlStrList, target);
+
+                        if (isSameAll) {
+                            noChanges.push(editchange);
+                        } else {
+                            // Need to find all conflicts when --force is used, track conflicting munges
+                            target.forEach(function (target_elem) {
+                                mungeutil.deep_add(conflictingMunge, change_file, conflictingParent, target_elem);
+                            });
+                        }
+
                     } else {
                         // plugin cannot overwrite other plugin changes without --force
                         conflictingPlugin = target[0].plugin;
@@ -397,7 +458,35 @@ function is_conflicting (editchanges, config_munge, self, force) {
         conflictingPlugin: conflictingPlugin,
         conflictingMunge: conflictingMunge,
         configxmlMunge: configxmlMunge,
-        conflictWithConfigxml: conflictWithConfigxml};
+        conflictWithConfigxml: conflictWithConfigxml,
+        noChanges: noChanges,
+        unusedConfigMunge: unusedConfigMunge };
+}
+
+function changedXmlStrList (editchange) {
+    var xmlStrList = [];
+    editchange.xmls.forEach(function (xml) {
+        var stringified = (new et.ElementTree(xml)).write({xml_declaration: false});
+        xmlStrList.push(stringified);
+    });
+    return xmlStrList;
+}
+
+// if all elements of target are same as xmlStrList, return true;
+// otherwise return false;
+function compareChangedXmlsAndTarget (xmlStrList, target) {
+    if (xmlStrList.length !== target.length) {
+        return false;
+    }
+    var isSame = target.reduce(function (acc, elem) {
+        var found1 = xmlStrList.find(function (x) { return x === elem.xml && elem.id === 'config.xml'; });
+        if (found1) {
+            return acc;
+        } else {
+            return false;
+        }
+    }, true);
+    return isSame;
 }
 
 // Go over the prepare queue and apply the config munges for each plugin
index a42c06b..1dd86d8 100644 (file)
@@ -190,6 +190,7 @@ function resolveConfigFilePath (project_dir, platform, file) {
     // only if none of the options above are satisfied does this get called
     // TODO: Move this out of cordova-common and into the platforms somehow
     if (platform === 'android' && !fs.existsSync(filepath)) {
+        var config_file;
         if (file === 'AndroidManifest.xml') {
             filepath = path.join(project_dir, 'app', 'src', 'main', 'AndroidManifest.xml');
         } else if (file.endsWith('config.xml')) {
@@ -197,9 +198,12 @@ function resolveConfigFilePath (project_dir, platform, file) {
         } else if (file.endsWith('strings.xml')) {
             // Plugins really shouldn't mess with strings.xml, since it's able to be localized
             filepath = path.join(project_dir, 'app', 'src', 'main', 'res', 'values', 'strings.xml');
+        } else if (file.includes(path.join('res', 'values'))) {
+            config_file = path.basename(file);
+            filepath = path.join(project_dir, 'app', 'src', 'main', 'res', 'values', config_file);
         } else if (file.includes(path.join('res', 'xml'))) {
             // Catch-all for all other stored XML configuration in legacy plugins
-            var config_file = path.basename(file);
+            config_file = path.basename(file);
             filepath = path.join(project_dir, 'app', 'src', 'main', 'res', 'xml', config_file);
         }
         return filepath;
index 62648d8..3c3854e 100644 (file)
@@ -86,6 +86,29 @@ exports.deep_find = function deep_find (obj, keys /* or key1, key2 .... */) {
 // When createParents is true, add the file and parent items  they are missing
 // When createParents is false, stop and return undefined if the file and/or parent items are missing
 
+exports.filterClone = function filterClone (obj, func) {
+    var result = { files: {} };
+    for (var file in obj.files) {
+        var parents = {};
+        // result.files[file] = { parents:{} };
+        for (var target in obj.files[file].parents) {
+            var list = [];
+            obj.files[file].parents[target].forEach(function (target_elem) {
+                if (func(target_elem)) {
+                    list.push(_.clone(target_elem, true));
+                }
+            });
+            if (list.length > 0) {
+                parents[target] = list;
+            }
+        }
+        if (Object.keys(parents).length > 0) {
+            result.files[file] = { parents: parents };
+        }
+    }
+    return result;
+};
+
 exports.process_munge = function process_munge (obj, createParents, func, keys /* or key1, key2 .... */) {
     if (!Array.isArray(keys)) {
         keys = Array.prototype.slice.call(arguments, 1);
index f0d26b5..9d6aeec 100644 (file)
@@ -562,7 +562,8 @@ ConfigParser.prototype = {
                     xmls: tag.getchildren(),
                     // To support demuxing via versions
                     versions: tag.attrib['versions'],
-                    deviceTarget: tag.attrib['device-target']
+                    deviceTarget: tag.attrib['device-target'],
+                    id: 'config.xml'
                 };
             return configFile;
         });