CB-11908 Add edit-config to config.xml
authorktop <ktop500@gmail.com>
Mon, 26 Sep 2016 21:07:09 +0000 (17:07 -0400)
committerSteve Gill <stevengill97@gmail.com>
Thu, 6 Oct 2016 23:30:25 +0000 (16:30 -0700)
This closes #492

src/ConfigChanges/ConfigChanges.js
src/ConfigChanges/ConfigFile.js
src/ConfigParser/ConfigParser.js
src/util/xml-helpers.js

index c9b9742..6a80730 100644 (file)
@@ -148,6 +148,11 @@ function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increm
     }
     else {
         var isConflictingInfo = is_conflicting(edit_config_changes, platform_config.config_munge, self, plugin_force);
+
+        if (isConflictingInfo.conflictWithConfigxml) {
+            throw new Error(pluginInfo.id +
+                ' cannot be added. <edit-config> changes in this plugin conflicts with <edit-config> changes in config.xml. Conflicts must be resolved before plugin can be added.');
+        }
         if (plugin_force) {
             CordovaLogger.get().log(CordovaLogger.WARN, '--force is used. edit-config will overwrite conflicts if any. Conflicting plugins may not work as expected.');
 
@@ -170,7 +175,67 @@ function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increm
             config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars, edit_config_changes);
         }
     }
-    // global munge looks at all plugins' changes to config files
+
+    self = munge_helper(should_increment, self, platform_config, config_munge);
+
+    // Move to installed/dependent_plugins
+    self.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level);
+    return self;
+}
+
+
+// Handle edit-config 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;
+
+    var config_munge;
+    var edit_config_changes = null;
+    if(config.getEditConfigs) {
+        edit_config_changes = config.getEditConfigs(self.platform);
+    }
+
+    if (!edit_config_changes || edit_config_changes.length === 0) {
+        // There are no edit-config changes to add, return here
+        return self;
+    }
+    else {
+        var isConflictingInfo = is_conflicting(edit_config_changes, platform_config.config_munge, self, true /*always force overwrite other edit-config*/);
+
+        if(isConflictingInfo.conflictFound) {
+            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);
+                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) {
+                CordovaLogger.get().log(CordovaLogger.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);
+                }
+            }
+        }
+        // Add config.xml edit-config munges
+        config_munge = self.generate_config_xml_munge(config, edit_config_changes, 'config.xml');
+    }
+
+    self = munge_helper(should_increment, self, platform_config, config_munge);
+
+    // Move to installed/dependent_plugins
+    return self;
+}
+
+function munge_helper(should_increment, self, platform_config, config_munge) {
+    // 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)
@@ -196,11 +261,10 @@ function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increm
             });
             /* jshint loopfunc:false */
         }
+
         self.apply_file_munge(file, munge.files[file]);
     }
 
-    // Move to installed/dependent_plugins
-    self.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level);
     return self;
 }
 
@@ -221,6 +285,39 @@ function reapply_global_munge () {
     return self;
 }
 
+// generate_plugin_config_munge
+// Generate the munge object from config.xml
+PlatformMunger.prototype.generate_config_xml_munge = generate_config_xml_munge;
+function generate_config_xml_munge(config, edit_config_changes, type) {
+
+    var munge = { files: {} };
+    var changes = edit_config_changes;
+    var id;
+
+    if(!changes) {
+        return munge;
+    }
+
+    if (type === 'config.xml') {
+        id = type;
+    }
+    else {
+        id = config.id;
+    }
+
+    changes.forEach(function(change) {
+        change.xmls.forEach(function(xml) {
+            // 1. stringify each xml
+            var stringified = (new et.ElementTree(xml)).write({xml_declaration:false});
+            // 2. add into munge
+            if (change.mode) {
+                mungeutil.deep_add(munge, change.file, change.target, { xml: stringified, count: 1, mode: change.mode, id: id });
+            }
+        });
+    });
+    return munge;
+}
+
 
 // generate_plugin_config_munge
 // Generate the munge object from plugin.xml + vars
@@ -335,7 +432,9 @@ function generate_plugin_config_munge(pluginInfo, vars, edit_config_changes) {
             }
             // 2. add into munge
             if (change.mode) {
-                mungeutil.deep_add(munge, change.file, change.target, { xml: stringified, count: 1, mode: change.mode, plugin: pluginInfo.id });
+                if (change.mode !== 'remove') {
+                    mungeutil.deep_add(munge, change.file, change.target, { xml: stringified, count: 1, mode: change.mode, plugin: pluginInfo.id });
+                }
             }
             else {
                 mungeutil.deep_add(munge, change.target, change.parent, { xml: stringified, count: 1, after: change.after });
@@ -348,7 +447,9 @@ function generate_plugin_config_munge(pluginInfo, vars, edit_config_changes) {
 function is_conflicting(editchanges, config_munge, self, force) {
     var files = config_munge.files;
     var conflictFound = false;
+    var conflictWithConfigxml = false;
     var conflictingMunge = { files: {} };
+    var configxmlMunge = { files: {} };
     var conflictingParent;
     var conflictingPlugin;
 
@@ -379,22 +480,42 @@ function is_conflicting(editchanges, config_munge, self, force) {
             }
 
             if (target && target.length !== 0) {
-                // conflict has been found, exit and throw an error
+                // conflict has been found
                 conflictFound = true;
-                if (!force) {
-                    // since there has been modifications to the attributes at this target,
-                    // the current plugin should not modify the attributes
-                    conflictingPlugin = target[0].plugin;
-                    return;
+
+                if (editchange.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]);
+                    }
+                    else {
+                        // Keep track of config.xml x plugin.xml edit-config conflicts
+                        mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
+                    }
                 }
+                else {
+                    if (target[0].id === 'config.xml') {
+                        // plugin.xml cannot overwrite config.xml changes even if --force is used
+                        conflictWithConfigxml = true;
+                        return;
+                    }
 
-                // need to find all conflicts when --force is used, track conflicting munges
-                mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
+                    if (force) {
+                        // Need to find all conflicts when --force is used, track conflicting munges
+                        mungeutil.deep_add(conflictingMunge, editchange.file, conflictingParent, target[0]);
+                    }
+                    else {
+                        // plugin cannot overwrite other plugin changes without --force
+                        conflictingPlugin = target[0].plugin;
+                        return;
+                    }
+                }
             }
         }
     });
 
-    return {conflictFound: conflictFound, conflictingPlugin: conflictingPlugin, conflictingMunge: conflictingMunge};
+    return {conflictFound: conflictFound, conflictingPlugin: conflictingPlugin, conflictingMunge: conflictingMunge,
+        configxmlMunge: configxmlMunge, conflictWithConfigxml:conflictWithConfigxml};
 }
 
 // Go over the prepare queue and apply the config munges for each plugin
index 594d6e4..4a58008 100644 (file)
@@ -110,6 +110,9 @@ ConfigFile.prototype.graft_child = function ConfigFile_graft_child(selector, xml
             case 'overwrite':
                 result = modules.xml_helpers.graftXMLOverwrite(self.data, xml_to_graft, selector, xml_child);
                 break;
+            case 'remove':
+                result= true;
+                break;
             default:
                 result = modules.xml_helpers.graftXML(self.data, xml_to_graft, selector, xml_child.after);
         }
@@ -137,6 +140,9 @@ ConfigFile.prototype.prune_child = function ConfigFile_prune_child(selector, xml
             case 'overwrite':
                 result = modules.xml_helpers.pruneXMLRestore(self.data, selector, xml_child);
                 break;
+            case 'remove':
+                result = modules.xml_helpers.prunXMLRemove(self.data, selector, xml_to_graft);
+                break;
             default:
                 result = modules.xml_helpers.pruneXML(self.data, xml_to_graft, selector);
         }
index bd417ac..f23df4d 100644 (file)
@@ -480,6 +480,25 @@ ConfigParser.prototype = {
             };
         });
     },
+    /* Get all edit-config tags */
+    getEditConfigs: function(platform) {
+        var platform_tag = this.doc.find('./platform[@name="' + platform + '"]');
+        var platform_edit_configs = platform_tag ? platform_tag.findall('edit-config') : [];
+
+        var edit_configs = this.doc.findall('edit-config').concat(platform_edit_configs);
+
+        return edit_configs.map(function(tag) {
+            var editConfig =
+                {
+                    file : tag.attrib['file'],
+                    target : tag.attrib['target'],
+                    mode : tag.attrib['mode'],
+                    id : 'config.xml',
+                    xmls : tag.getchildren()
+                };
+            return editConfig;
+        });
+    },
     write:function() {
         fs.writeFileSync(this.path, this.doc.write({indent: 4}), 'utf-8');
     }
index 4b630fa..9a1e82d 100644 (file)
@@ -160,6 +160,23 @@ module.exports = {
         return true;
     },
 
+    prunXMLRemove: function(doc, selector, nodes) {
+        var target = module.exports.resolveParent(doc, selector);
+        if (!target) return false;
+
+        nodes.forEach(function (node) {
+            var attributes = node.attrib;
+            for (var attribute in attributes) {
+                if (target.attrib[attribute]) {
+                    delete target.attrib[attribute];
+                }
+            }
+        });
+
+        return true;
+
+    },
+
 
     parseElementtreeSync: function (filename) {
         var contents = fs.readFileSync(filename, 'utf-8');