You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by er...@apache.org on 2018/12/10 06:40:30 UTC

[cordova-ios] branch master updated: Cocoapods support improvement, using podspec tag in plugin.xml (#405)

This is an automated email from the ASF dual-hosted git repository.

erisu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-ios.git


The following commit(s) were added to refs/heads/master by this push:
     new 65015c1  Cocoapods support improvement, using podspec tag in plugin.xml (#405)
65015c1 is described below

commit 65015c12a3c8aa9577a53d0246da43eda7e7b3ab
Author: Ken Naito <fi...@gmail.com>
AuthorDate: Mon Dec 10 15:40:24 2018 +0900

    Cocoapods support improvement, using podspec tag in plugin.xml (#405)
---
 bin/templates/scripts/cordova/Api.js               | 387 +++++++++++++-----
 bin/templates/scripts/cordova/lib/Podfile.js       | 212 +++++++++-
 bin/templates/scripts/cordova/lib/PodsJson.js      | 142 +++++--
 .../scripts/cordova/lib/plugman/pluginHandlers.js  |  11 +-
 tests/spec/unit/Api.spec.js                        | 447 ++++++++++++++++++++-
 tests/spec/unit/Podfile.spec.js                    |  16 +-
 tests/spec/unit/PodsJson.spec.js                   | 149 +++++--
 tests/spec/unit/prepare.spec.js                    |  24 +-
 8 files changed, 1173 insertions(+), 215 deletions(-)

diff --git a/bin/templates/scripts/cordova/Api.js b/bin/templates/scripts/cordova/Api.js
index 9a1da6a..e193c3c 100644
--- a/bin/templates/scripts/cordova/Api.js
+++ b/bin/templates/scripts/cordova/Api.js
@@ -30,6 +30,7 @@ var events = require('cordova-common').events;
 var PluginManager = require('cordova-common').PluginManager;
 var Q = require('q');
 var util = require('util');
+var xcode = require('xcode');
 var ConfigParser = require('cordova-common').ConfigParser;
 
 function setupEvents (externalEventEmitter) {
@@ -221,6 +222,7 @@ Api.prototype.prepare = function (cordovaProject) {
  *   CordovaError instance.
  */
 Api.prototype.addPlugin = function (plugin, installOptions) {
+
     var xcodeproj = projectFile.parse(this.locations);
     var self = this;
 
@@ -254,64 +256,13 @@ Api.prototype.addPlugin = function (plugin, installOptions) {
             }
         })
         .then(function () {
-            var frameworkTags = plugin.getFrameworks(self.platform);
-            var frameworkPods = frameworkTags.filter(function (obj) {
-                return (obj.type === 'podspec');
-            });
-
-            return Q.resolve(frameworkPods);
-        })
-        .then(function (frameworkPods) {
-            if (!(frameworkPods.length)) {
-                return Q.resolve();
-            }
-
-            var project_dir = self.locations.root;
-            var project_name = self.locations.xcodeCordovaProj.split('/').pop();
-            var minDeploymentTarget = self.getPlatformInfo().projectConfig.getPreference('deployment-target', 'ios');
-
-            var Podfile = require('./lib/Podfile').Podfile;
-            var PodsJson = require('./lib/PodsJson').PodsJson;
-
-            events.emit('verbose', 'Adding pods since the plugin contained <framework>(s) with type="podspec"');
-
-            var podsjsonFile = new PodsJson(path.join(project_dir, PodsJson.FILENAME));
-            var podfileFile = new Podfile(path.join(project_dir, Podfile.FILENAME), project_name, minDeploymentTarget);
-
-            frameworkPods.forEach(function (obj) {
-                var podJson = {
-                    name: obj.src,
-                    type: obj.type,
-                    spec: obj.spec
-                };
-
-                var val = podsjsonFile.get(podJson.name);
-                if (val) { // found
-                    if (podJson.spec !== val.spec) { // exists, different spec, print warning
-                        events.emit('warn', plugin.id + ' depends on ' + podJson.name + '@' + podJson.spec + ', which conflicts with another plugin. ' + podJson.name + '@' + val.spec + ' is already installed and was not overwritten.');
-                    }
-                    // increment count, but don't add in Podfile because it already exists
-                    podsjsonFile.increment(podJson.name);
-                } else { // not found, write new
-                    podJson.count = 1;
-                    podsjsonFile.setJson(podJson.name, podJson);
-                    // add to Podfile
-                    podfileFile.addSpec(podJson.name, podJson.spec);
-                }
-            });
-
-            // now that all the pods have been processed, write to pods.json
-            podsjsonFile.write();
-
-            // only write and pod install if the Podfile changed
-            if (podfileFile.isDirty()) {
-                podfileFile.write();
-                events.emit('verbose', 'Running `pod install` (to install plugins)');
-                projectFile.purgeProjectFileCache(self.locations.root);
-
-                return podfileFile.install(check_reqs.check_cocoapods);
-            } else {
-                events.emit('verbose', 'Podfile unchanged, skipping `pod install`');
+            if (plugin != null) {
+                var podSpecs = plugin.getPodSpecs(self.platform);
+                var frameworkTags = plugin.getFrameworks(self.platform);
+                var frameworkPods = frameworkTags.filter(function (obj) {
+                    return (obj.type === 'podspec');
+                });
+                return self.addPodSpecs(plugin, podSpecs, frameworkPods);
             }
         })
         // CB-11022 return non-falsy value to indicate
@@ -359,63 +310,303 @@ Api.prototype.removePlugin = function (plugin, uninstallOptions) {
             }
         })
         .then(function () {
-            var frameworkTags = plugin.getFrameworks(self.platform);
-            var frameworkPods = frameworkTags.filter(function (obj) {
-                return (obj.type === 'podspec');
+            if (plugin != null) {
+                var podSpecs = plugin.getPodSpecs(self.platform);
+                var frameworkTags = plugin.getFrameworks(self.platform);
+                var frameworkPods = frameworkTags.filter(function (obj) {
+                    return (obj.type === 'podspec');
+                });
+                return self.removePodSpecs(plugin, podSpecs, frameworkPods);
+            }
+        })
+        // CB-11022 return non-falsy value to indicate
+        // that there is no need to run prepare after
+        .thenResolve(true);
+};
+
+/**
+ * adding CocoaPods libraries
+ *
+ * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
+ *   that will be installed.
+ * @param  {Object}  podSpecs: the return value of plugin.getPodSpecs(self.platform)
+ * @param  {Object}  frameworkPods: framework tags object with type === 'podspec'
+ * @return  {Promise}  Return a promise
+ */
+
+Api.prototype.addPodSpecs = function (plugin, podSpecs, frameworkPods) {
+    var self = this;
+
+    var project_dir = self.locations.root;
+    var project_name = self.locations.xcodeCordovaProj.split('/').pop();
+    var minDeploymentTarget = self.getPlatformInfo().projectConfig.getPreference('deployment-target', 'ios');
+
+    var Podfile = require('./lib/Podfile').Podfile;
+    var PodsJson = require('./lib/PodsJson').PodsJson;
+    var podsjsonFile = new PodsJson(path.join(project_dir, PodsJson.FILENAME));
+    var podfileFile = new Podfile(path.join(project_dir, Podfile.FILENAME), project_name, minDeploymentTarget);
+
+    if (podSpecs.length) {
+        events.emit('verbose', 'Adding pods since the plugin contained <podspecs>');
+        podSpecs.forEach(function (obj) {
+            // declarations
+            Object.keys(obj.declarations).forEach(function (key) {
+                if (obj.declarations[key] === 'true') {
+                    var declaration = Podfile.proofDeclaration(key);
+                    var podJson = {
+                        declaration: declaration
+                    };
+                    var val = podsjsonFile.getDeclaration(declaration);
+                    if (val) {
+                        podsjsonFile.incrementDeclaration(declaration);
+                    } else {
+                        podJson.count = 1;
+                        podsjsonFile.setJsonDeclaration(declaration, podJson);
+                        podfileFile.addDeclaration(podJson.declaration);
+                    }
+                }
+            });
+            // sources
+            Object.keys(obj.sources).forEach(function (key) {
+                var podJson = {
+                    source: obj.sources[key].source
+                };
+                var val = podsjsonFile.getSource(key);
+                if (val) {
+                    podsjsonFile.incrementSource(key);
+                } else {
+                    podJson.count = 1;
+                    podsjsonFile.setJsonSource(key, podJson);
+                    podfileFile.addSource(podJson.source);
+                }
+            });
+            // libraries
+            Object.keys(obj.libraries).forEach(function (key) {
+                var podJson = Object.assign({}, obj.libraries[key]);
+                var val = podsjsonFile.getLibrary(key);
+                if (val) {
+                    events.emit('warn', plugin.id + ' depends on ' + podJson.name + ', which may conflict with another plugin. ' + podJson.name + '@' + val.spec + ' is already installed and was not overwritten.');
+                    podsjsonFile.incrementLibrary(key);
+                } else {
+                    podJson.count = 1;
+                    podsjsonFile.setJsonLibrary(key, podJson);
+                    podfileFile.addSpec(podJson.name, podJson);
+                }
             });
 
-            return Q.resolve(frameworkPods);
-        })
-        .then(function (frameworkPods) {
-            if (!(frameworkPods.length)) {
-                return Q.resolve();
+        });
+    }
+
+    if (frameworkPods.length) {
+        events.emit('warn', '"framework" tag with type "podspec" is deprecated and will be removed. Please use the "podspec" tag.');
+        events.emit('verbose', 'Adding pods since the plugin contained <framework>(s) with type="podspec"');
+        frameworkPods.forEach(function (obj) {
+            var podJson = {
+                name: obj.src,
+                type: obj.type,
+                spec: obj.spec
+            };
+
+            var val = podsjsonFile.getLibrary(podJson.name);
+            if (val) { // found
+                if (podJson.spec !== val.spec) { // exists, different spec, print warning
+                    events.emit('warn', plugin.id + ' depends on ' + podJson.name + '@' + podJson.spec + ', which conflicts with another plugin. ' + podJson.name + '@' + val.spec + ' is already installed and was not overwritten.');
+                }
+                // increment count, but don't add in Podfile because it already exists
+                podsjsonFile.incrementLibrary(podJson.name);
+            } else { // not found, write new
+                podJson.count = 1;
+                podsjsonFile.setJsonLibrary(podJson.name, podJson);
+                // add to Podfile
+                podfileFile.addSpec(podJson.name, podJson.spec);
             }
+        });
+    }
 
-            var project_dir = self.locations.root;
-            var project_name = self.locations.xcodeCordovaProj.split('/').pop();
+    if (podSpecs.length > 0 || frameworkPods.length > 0) {
+        // now that all the pods have been processed, write to pods.json
+        podsjsonFile.write();
 
-            var Podfile = require('./lib/Podfile').Podfile;
-            var PodsJson = require('./lib/PodsJson').PodsJson;
+        // only write and pod install if the Podfile changed
+        if (podfileFile.isDirty()) {
+            podfileFile.write();
+            events.emit('verbose', 'Running `pod install` (to install plugins)');
+            projectFile.purgeProjectFileCache(self.locations.root);
 
-            events.emit('verbose', 'Adding pods since the plugin contained <framework>(s) with type=\"podspec\"'); /* eslint no-useless-escape : 0 */
+            return podfileFile.install(check_reqs.check_cocoapods)
+                .then(function () {
+                    self.setSwiftVersionForCocoaPodsLibraries(podsjsonFile);
+                });
+        } else {
+            events.emit('verbose', 'Podfile unchanged, skipping `pod install`');
+        }
+    }
+    return Q.when();
+};
+
+/**
+ * removing CocoaPods libraries
+ *
+ * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
+ *   that will be installed.
+ * @param  {Object}  podSpecs: the return value of plugin.getPodSpecs(self.platform)
+ * @param  {Object}  frameworkPods: framework tags object with type === 'podspec'
+ * @return  {Promise}  Return a promise
+ */
 
-            var podsjsonFile = new PodsJson(path.join(project_dir, PodsJson.FILENAME));
-            var podfileFile = new Podfile(path.join(project_dir, Podfile.FILENAME), project_name);
+Api.prototype.removePodSpecs = function (plugin, podSpecs, frameworkPods) {
+    var self = this;
 
-            frameworkPods.forEach(function (obj) {
+    var project_dir = self.locations.root;
+    var project_name = self.locations.xcodeCordovaProj.split('/').pop();
+
+    var Podfile = require('./lib/Podfile').Podfile;
+    var PodsJson = require('./lib/PodsJson').PodsJson;
+    var podsjsonFile = new PodsJson(path.join(project_dir, PodsJson.FILENAME));
+    var podfileFile = new Podfile(path.join(project_dir, Podfile.FILENAME), project_name);
+
+    if (podSpecs.length) {
+        events.emit('verbose', 'Adding pods since the plugin contained <podspecs>');
+        podSpecs.forEach(function (obj) {
+            // declarations
+            Object.keys(obj.declarations).forEach(function (key) {
+                if (obj.declarations[key] === 'true') {
+                    var declaration = Podfile.proofDeclaration(key);
+                    var podJson = {
+                        declaration: declaration
+                    };
+                    var val = podsjsonFile.getDeclaration(declaration);
+                    if (val) {
+                        podsjsonFile.decrementDeclaration(declaration);
+                    } else {
+                        var message = util.format('plugin \"%s\" declaration \"%s\" does not seem to be in pods.json, nothing to remove. Will attempt to remove from Podfile.', plugin.id, podJson.declaration); /* eslint no-useless-escape : 0 */
+                        events.emit('verbose', message);
+                    }
+                    if (!val || val.count === 0) {
+                        podfileFile.removeDeclaration(podJson.declaration);
+                    }
+                }
+            });
+            // sources
+            Object.keys(obj.sources).forEach(function (key) {
                 var podJson = {
-                    name: obj.src,
-                    type: obj.type,
-                    spec: obj.spec
+                    source: obj.sources[key].source
                 };
-
-                var val = podsjsonFile.get(podJson.name);
-                if (val) { // found, decrement count
-                    podsjsonFile.decrement(podJson.name);
-                } else { // not found (perhaps a sync error)
+                var val = podsjsonFile.getSource(key);
+                if (val) {
+                    podsjsonFile.decrementSource(key);
+                } else {
+                    var message = util.format('plugin \"%s\" source \"%s\" does not seem to be in pods.json, nothing to remove. Will attempt to remove from Podfile.', plugin.id, podJson.source); /* eslint no-useless-escape : 0 */
+                    events.emit('verbose', message);
+                }
+                if (!val || val.count === 0) {
+                    podfileFile.removeSource(podJson.source);
+                }
+            });
+            // libraries
+            Object.keys(obj.libraries).forEach(function (key) {
+                var podJson = Object.assign({}, obj.libraries[key]);
+                var val = podsjsonFile.getLibrary(key);
+                if (val) {
+                    podsjsonFile.decrementLibrary(key);
+                } else {
                     var message = util.format('plugin \"%s\" podspec \"%s\" does not seem to be in pods.json, nothing to remove. Will attempt to remove from Podfile.', plugin.id, podJson.name); /* eslint no-useless-escape : 0 */
                     events.emit('verbose', message);
                 }
-
-                // always remove from the Podfile
-                podfileFile.removeSpec(podJson.name);
+                if (!val || val.count === 0) {
+                    podfileFile.removeSpec(podJson.name);
+                }
             });
 
-            // now that all the pods have been processed, write to pods.json
-            podsjsonFile.write();
-
-            if (podfileFile.isDirty()) {
-                podfileFile.write();
-                events.emit('verbose', 'Running `pod install` (to uninstall pods)');
+        });
+    }
 
-                return podfileFile.install(check_reqs.check_cocoapods);
-            } else {
-                events.emit('verbose', 'Podfile unchanged, skipping `pod install`');
+    if (frameworkPods.length) {
+        events.emit('warn', '"framework" tag with type "podspec" is deprecated and will be removed. Please use the "podspec" tag.');
+        events.emit('verbose', 'Adding pods since the plugin contained <framework>(s) with type=\"podspec\"'); /* eslint no-useless-escape : 0 */
+        frameworkPods.forEach(function (obj) {
+            var podJson = {
+                name: obj.src,
+                type: obj.type,
+                spec: obj.spec
+            };
+
+            var val = podsjsonFile.getLibrary(podJson.name);
+            if (val) { // found, decrement count
+                podsjsonFile.decrementLibrary(podJson.name);
+            } else { // not found (perhaps a sync error)
+                var message = util.format('plugin \"%s\" podspec \"%s\" does not seem to be in pods.json, nothing to remove. Will attempt to remove from Podfile.', plugin.id, podJson.name); /* eslint no-useless-escape : 0 */
+                events.emit('verbose', message);
             }
-        })
-        // CB-11022 return non-falsy value to indicate
-        // that there is no need to run prepare after
-        .thenResolve(true);
+
+            // always remove from the Podfile
+            podfileFile.removeSpec(podJson.name);
+        });
+    }
+
+    if (podSpecs.length > 0 || frameworkPods.length > 0) {
+        // now that all the pods have been processed, write to pods.json
+        podsjsonFile.write();
+
+        if (podfileFile.isDirty()) {
+            podfileFile.write();
+            events.emit('verbose', 'Running `pod install` (to uninstall pods)');
+
+            return podfileFile.install(check_reqs.check_cocoapods)
+                .then(function () {
+                    self.setSwiftVersionForCocoaPodsLibraries(podsjsonFile);
+                });
+        } else {
+            events.emit('verbose', 'Podfile unchanged, skipping `pod install`');
+        }
+    }
+    return Q.when();
+};
+
+/**
+ * set Swift Version for all CocoaPods libraries
+ *
+ * @param  {PodsJson}  podsjsonFile  A PodsJson instance that represents pods.json
+ */
+
+Api.prototype.setSwiftVersionForCocoaPodsLibraries = function (podsjsonFile) {
+    var self = this;
+    var __dirty = false;
+    var podPbxPath = path.join(self.root, 'Pods', 'Pods.xcodeproj', 'project.pbxproj');
+    var podXcodeproj = xcode.project(podPbxPath);
+    podXcodeproj.parseSync();
+    var podTargets = podXcodeproj.pbxNativeTargetSection();
+    var podConfigurationList = podXcodeproj.pbxXCConfigurationList();
+    var podConfigs = podXcodeproj.pbxXCBuildConfigurationSection();
+
+    var libraries = podsjsonFile.getLibraries();
+    Object.keys(libraries).forEach(function (key) {
+        var podJson = libraries[key];
+        var name = podJson.name;
+        var swiftVersion = podJson['swift-version'];
+        if (swiftVersion) {
+            __dirty = true;
+            Object.keys(podTargets).filter(function (targetKey) {
+                return podTargets[targetKey].productName === name;
+            }).map(function (targetKey) {
+                return podTargets[targetKey].buildConfigurationList;
+            }).map(function (buildConfigurationListId) {
+                return podConfigurationList[buildConfigurationListId];
+            }).map(function (buildConfigurationList) {
+                return buildConfigurationList.buildConfigurations;
+            }).reduce(function (acc, buildConfigurations) {
+                return acc.concat(buildConfigurations);
+            }, []).map(function (buildConfiguration) {
+                return buildConfiguration.value;
+            }).forEach(function (buildId) {
+                __dirty = true;
+                podConfigs[buildId].buildSettings['SWIFT_VERSION'] = swiftVersion;
+            });
+        }
+    });
+    if (__dirty) {
+        fs.writeFileSync(podPbxPath, podXcodeproj.writeSync(), 'utf-8');
+    }
 };
 
 /**
diff --git a/bin/templates/scripts/cordova/lib/Podfile.js b/bin/templates/scripts/cordova/lib/Podfile.js
index ffa5be2..2e946f7 100644
--- a/bin/templates/scripts/cordova/lib/Podfile.js
+++ b/bin/templates/scripts/cordova/lib/Podfile.js
@@ -27,14 +27,22 @@ var superspawn = require('cordova-common').superspawn;
 var CordovaError = require('cordova-common').CordovaError;
 
 Podfile.FILENAME = 'Podfile';
+Podfile.declarationRegexpMap = {
+    'use_frameworks!': 'use[-_]frameworks!?',
+    'inhibit_all_warnings!': 'inhibit[-_]all[-_]warnings!?'
+};
 
 function Podfile (podFilePath, projectName, minDeploymentTarget) {
+    this.declarationToken = '##INSERT_DECLARATION##';
+    this.sourceToken = '##INSERT_SOURCE##';
     this.podToken = '##INSERT_POD##';
 
     this.path = podFilePath;
     this.projectName = projectName;
     this.minDeploymentTarget = minDeploymentTarget || '9.0';
     this.contents = null;
+    this.sources = null;
+    this.declarations = null;
     this.pods = null;
     this.__dirty = false;
 
@@ -56,10 +64,72 @@ function Podfile (podFilePath, projectName, minDeploymentTarget) {
     } else {
         events.emit('verbose', 'Podfile found in platforms/ios');
         // parse for pods
-        this.pods = this.__parseForPods(fs.readFileSync(this.path, 'utf8'));
+        var fileText = fs.readFileSync(this.path, 'utf8');
+        this.declarations = this.__parseForDeclarations(fileText);
+        this.sources = this.__parseForSources(fileText);
+        this.pods = this.__parseForPods(fileText);
     }
 }
 
+Podfile.prototype.__parseForDeclarations = function (text) {
+    // split by \n
+    var arr = text.split('\n');
+
+    // getting lines between "platform :ios, '9.0'"" and "target 'HelloCordova'" do
+    var declarationsPreRE = new RegExp('platform :ios,\\s+\'[^\']+\'');
+    var declarationsPostRE = new RegExp('target\\s+\'[^\']+\'\\s+do');
+    var declarationRE = new RegExp('^\\s*[^#]');
+
+    return arr.reduce(function (acc, line) {
+        switch (acc.state) {
+        case 0:
+            if (declarationsPreRE.exec(line)) {
+                acc.state = 1; // Start to read
+            }
+            break;
+        case 1:
+            if (declarationsPostRE.exec(line)) {
+                acc.state = 2; // Finish to read
+            } else {
+                acc.lines.push(line);
+            }
+            break;
+        case 2:
+        default:
+            // do nothing;
+        }
+        return acc;
+    }, {state: 0, lines: []})
+        .lines
+        .filter(function (line) {
+            return declarationRE.exec(line);
+        })
+        .reduce(function (obj, line) {
+            obj[line] = line;
+            return obj;
+        }, {});
+};
+
+Podfile.prototype.__parseForSources = function (text) {
+    // split by \n
+    var arr = text.split('\n');
+
+    var sourceRE = new RegExp('source \'(.*)\'');
+    return arr.filter(function (line) {
+        var m = sourceRE.exec(line);
+
+        return (m !== null);
+    })
+        .reduce(function (obj, line) {
+            var m = sourceRE.exec(line);
+            if (m !== null) {
+                var source = m[1];
+                obj[source] = source;
+            }
+            return obj;
+        }, {});
+};
+
 Podfile.prototype.__parseForPods = function (text) {
     // split by \n
     var arr = text.split('\n');
@@ -68,7 +138,8 @@ Podfile.prototype.__parseForPods = function (text) {
     //     pod 'Foobar', '1.2'
     //     pod 'Foobar', 'abc 123 1.2'
     //     pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
-    var podRE = new RegExp('pod \'([^\']*)\'\\s*,?\\s*(.*)');
+    // var podRE = new RegExp('pod \'([^\']*)\'\\s*,?\\s*(.*)');
+    var podRE = new RegExp('pod \'([^\']*)\'\\s*(?:,\\s*\'([^\']*)\'\\s*)?,?\\s*(.*)');
 
     // only grab lines that don't have the pod spec'
     return arr.filter(function (line) {
@@ -80,9 +151,16 @@ Podfile.prototype.__parseForPods = function (text) {
             var m = podRE.exec(line);
 
             if (m !== null) {
-                // strip out any single quotes around the value m[2]
-                var podSpec = m[2].replace(/^\'|\'$/g, ''); /* eslint no-useless-escape : 0 */
-                obj[m[1]] = podSpec; // i.e pod 'Foo', '1.2' ==> { 'Foo' : '1.2'}
+                var podspec = {
+                    name: m[1]
+                };
+                if (m[2]) {
+                    podspec.spec = m[2];
+                }
+                if (m[3]) {
+                    podspec.options = m[3];
+                }
+                obj[m[1]] = podspec; // i.e pod 'Foo', '1.2' ==> { 'Foo' : '1.2'}
             }
 
             return obj;
@@ -98,12 +176,14 @@ Podfile.prototype.getTemplate = function () {
     var projectName = this.escapeSingleQuotes(this.projectName);
     return util.format(
         '# DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
+            '%s\n' +
             'platform :ios, \'%s\'\n' +
+            '%s\n' +
             'target \'%s\' do\n' +
             '\tproject \'%s.xcodeproj\'\n' +
             '%s\n' +
             'end\n',
-        this.minDeploymentTarget, projectName, projectName, this.podToken);
+        this.sourceToken, this.minDeploymentTarget, this.declarationToken, projectName, projectName, this.podToken);
 };
 
 Podfile.prototype.addSpec = function (name, spec) {
@@ -115,6 +195,14 @@ Podfile.prototype.addSpec = function (name, spec) {
         throw new CordovaError('Podfile addSpec: name is not specified.');
     }
 
+    if (typeof spec === 'string') {
+        if (spec.startsWith(':')) {
+            spec = {name: name, options: spec};
+        } else {
+            spec = {name: name, spec: spec};
+        }
+    }
+
     this.pods[name] = spec;
     this.__dirty = true;
 
@@ -138,7 +226,60 @@ Podfile.prototype.existsSpec = function (name) {
     return (name in this.pods);
 };
 
+Podfile.prototype.addSource = function (src) {
+    this.sources[src] = src;
+    this.__dirty = true;
+
+    events.emit('verbose', util.format('Added source line for `%s`', src));
+};
+
+Podfile.prototype.removeSource = function (src) {
+    if (this.existsSource(src)) {
+        delete this.sources[src];
+        this.__dirty = true;
+    }
+
+    events.emit('verbose', util.format('Removed source line for `%s`', src));
+};
+
+Podfile.prototype.existsSource = function (src) {
+    return (src in this.sources);
+};
+
+Podfile.prototype.addDeclaration = function (declaration) {
+    this.declarations[declaration] = declaration;
+    this.__dirty = true;
+
+    events.emit('verbose', util.format('Added declaration line for `%s`', declaration));
+};
+
+Podfile.prototype.removeDeclaration = function (declaration) {
+    if (this.existsDeclaration(declaration)) {
+        delete this.declarations[declaration];
+        this.__dirty = true;
+    }
+
+    events.emit('verbose', util.format('Removed source line for `%s`', declaration));
+};
+
+Podfile.proofDeclaration = function (declaration) {
+    var list = Object.keys(Podfile.declarationRegexpMap).filter(function (key) {
+        var regexp = new RegExp(Podfile.declarationRegexpMap[key]);
+        return regexp.test(declaration);
+    });
+    if (list.length > 0) {
+        return list[0];
+    }
+    return declaration;
+};
+
+Podfile.prototype.existsDeclaration = function (declaration) {
+    return (declaration in this.declarations);
+};
+
 Podfile.prototype.clear = function () {
+    this.sources = {};
+    this.declarations = {};
     this.pods = {};
     this.__dirty = true;
 };
@@ -155,22 +296,61 @@ Podfile.prototype.write = function () {
     var podsString =
     Object.keys(this.pods).map(function (key) {
         var name = key;
-        var spec = self.pods[key];
-
-        if (spec.length) {
-            if (spec.indexOf(':') === 0) {
-                // don't quote it, it's a specification (starts with ':')
-                return util.format('\tpod \'%s\', %s', name, spec);
+        var json = self.pods[key];
+
+        if (typeof json === 'string') { // compatibility for using framework tag.
+            var spec = json;
+            if (spec.length) {
+                if (spec.indexOf(':') === 0) {
+                    // don't quote it, it's a specification (starts with ':')
+                    return util.format('\tpod \'%s\', %s', name, spec);
+                } else {
+                    // quote it, it's a version
+                    return util.format('\tpod \'%s\', \'%s\'', name, spec);
+                }
             } else {
-                // quote it, it's a version
-                return util.format('\tpod \'%s\', \'%s\'', name, spec);
+                return util.format('\tpod \'%s\'', name);
             }
         } else {
-            return util.format('\tpod \'%s\'', name);
+            var list = ['\'' + name + '\''];
+            if ('spec' in json) {
+                list.push('\'' + json.spec + '\'');
+            }
+
+            var options = ['tag', 'branch', 'commit', 'git', 'podspec'].filter(function (tag) {
+                return tag in json;
+            }).map(function (tag) {
+                return ':' + tag + ' => \'' + json[tag] + '\'';
+            });
+            if ('configurations' in json) {
+                options.push(':configurations => [' + json['configurations'].split(',').map(function (conf) { return '\'' + conf.trim() + '\''; }).join(',') + ']');
+            }
+            if ('options' in json) {
+                options = [json.options];
+            }
+            if (options.length > 0) {
+                list.push(options.join(', '));
+            }
+            return util.format('\tpod %s', list.join(', '));
         }
     }).join('\n');
 
-    text = text.replace(this.podToken, podsString);
+    var sourcesString =
+    Object.keys(this.sources).map(function (key) {
+        var source = self.sources[key];
+        return util.format('source \'%s\'', source);
+    }).join('\n');
+
+    var declarationString =
+    Object.keys(this.declarations).map(function (key) {
+        var declaration = self.declarations[key];
+        return declaration;
+    }).join('\n');
+
+    text = text.replace(this.podToken, podsString)
+        .replace(this.sourceToken, sourcesString)
+        .replace(this.declarationToken, declarationString);
+
     fs.writeFileSync(this.path, text, 'utf8');
     this.__dirty = false;
 
diff --git a/bin/templates/scripts/cordova/lib/PodsJson.js b/bin/templates/scripts/cordova/lib/PodsJson.js
index 0470527..25afbac 100644
--- a/bin/templates/scripts/cordova/lib/PodsJson.js
+++ b/bin/templates/scripts/cordova/lib/PodsJson.js
@@ -24,6 +24,9 @@ var events = require('cordova-common').events;
 var CordovaError = require('cordova-common').CordovaError;
 
 PodsJson.FILENAME = 'pods.json';
+PodsJson.LIBRARY = 'libraries';
+PodsJson.SOURCE = 'sources';
+PodsJson.DECLARATION = 'declarations';
 
 function PodsJson (podsJsonPath) {
     this.path = podsJsonPath;
@@ -43,25 +46,81 @@ function PodsJson (podsJsonPath) {
     } else {
         events.emit('verbose', 'pods.json found in platforms/ios');
         // load contents
-        this.contents = fs.readFileSync(this.path, 'utf8');
-        this.contents = JSON.parse(this.contents);
+        var contents = fs.readFileSync(this.path, 'utf8');
+        this.contents = JSON.parse(contents);
     }
+    this.__updateFormatIfNecessary();
 }
 
-PodsJson.prototype.get = function (name) {
-    return this.contents[name];
+PodsJson.prototype.__isOldFormat = function () {
+    if (this.contents !== null) {
+        if (this.contents.declarations === undefined ||
+            this.contents.sources === undefined ||
+            this.contents.libraries === undefined) {
+            return true;
+        }
+    }
+    return false;
 };
 
-PodsJson.prototype.remove = function (name) {
-    if (this.contents[name]) {
-        delete this.contents[name];
+PodsJson.prototype.__updateFormatIfNecessary = function () {
+    if (this.__isOldFormat()) {
+        this.contents = {
+            declarations: {},
+            sources: {},
+            libraries: this.contents
+        };
         this.__dirty = true;
-        events.emit('verbose', util.format('Remove from pods.json for `%s`', name));
+        events.emit('verbose', 'Update format of pods.json');
     }
 };
 
+PodsJson.prototype.getLibraries = function () {
+    return this.contents[PodsJson.LIBRARY];
+};
+
+PodsJson.prototype.__get = function (kind, name) {
+    return this.contents[kind][name];
+};
+
+PodsJson.prototype.getLibrary = function (name) {
+    return this.__get(PodsJson.LIBRARY, name);
+};
+
+PodsJson.prototype.getSource = function (name) {
+    return this.__get(PodsJson.SOURCE, name);
+};
+
+PodsJson.prototype.getDeclaration = function (name) {
+    return this.__get(PodsJson.DECLARATION, name);
+};
+
+PodsJson.prototype.__remove = function (kind, name) {
+    if (this.contents[kind][name]) {
+        delete this.contents[kind][name];
+        this.__dirty = true;
+        events.emit('verbose', util.format('Remove from pods.json for `%s` - `%s`', name));
+    }
+};
+
+PodsJson.prototype.removeLibrary = function (name) {
+    this.__remove(PodsJson.LIBRARY, name);
+};
+
+PodsJson.prototype.removeSource = function (name) {
+    this.__remove(PodsJson.SOURCE, name);
+};
+
+PodsJson.prototype.removeDeclaration = function (name) {
+    this.__remove(PodsJson.DECLARATION, name);
+};
+
 PodsJson.prototype.clear = function () {
-    this.contents = {};
+    this.contents = {
+        declarations: {},
+        sources: {},
+        libraries: {}
+    };
     this.__dirty = true;
 };
 
@@ -78,34 +137,69 @@ PodsJson.prototype.write = function () {
     }
 };
 
-PodsJson.prototype.set = function (name, type, spec, count) {
-    this.setJson(name, { name: name, type: type, spec: spec, count: count });
-};
-
-PodsJson.prototype.increment = function (name) {
-    var val = this.get(name);
+PodsJson.prototype.__increment = function (kind, name) {
+    var val = this.__get(kind, name);
     if (val) {
         val.count++;
-        this.setJson(val);
     }
 };
 
-PodsJson.prototype.decrement = function (name) {
-    var val = this.get(name);
+PodsJson.prototype.incrementLibrary = function (name) {
+    this.__increment(PodsJson.LIBRARY, name);
+};
+
+PodsJson.prototype.incrementSource = function (name) {
+    this.__increment(PodsJson.SOURCE, name);
+};
+
+PodsJson.prototype.incrementDeclaration = function (name) {
+    this.__increment(PodsJson.DECLARATION, name);
+};
+
+PodsJson.prototype.__decrement = function (kind, name) {
+    var val = this.__get(kind, name);
     if (val) {
         val.count--;
         if (val.count <= 0) {
-            this.remove(name);
-        } else {
-            this.setJson(val);
+            this.__remove(kind, name);
         }
     }
 };
 
-PodsJson.prototype.setJson = function (name, json) {
-    this.contents[name] = json;
+PodsJson.prototype.decrementLibrary = function (name) {
+    this.__decrement(PodsJson.LIBRARY, name);
+};
+
+PodsJson.prototype.decrementSource = function (name) {
+    this.__decrement(PodsJson.SOURCE, name);
+};
+
+PodsJson.prototype.decrementDeclaration = function (name) {
+    this.__decrement(PodsJson.DECLARATION, name);
+};
+
+PodsJson.prototype.__setJson = function (kind, name, json) {
+    this.contents[kind][name] = Object.assign({}, json);
     this.__dirty = true;
-    events.emit('verbose', util.format('Set pods.json for `%s`', name));
+    events.emit('verbose', util.format('Set pods.json for `%s` - `%s`', kind, name));
+};
+
+// sample json for library
+// { name: "Eureka", spec: "4.2.0", "swift-version": "4.1", count: 1 }
+PodsJson.prototype.setJsonLibrary = function (name, json) {
+    this.__setJson(PodsJson.LIBRARY, name, json);
+};
+
+// sample json for source
+// { source: "https://github.com/brightcove/BrightcoveSpecs.git", count: 1 }
+PodsJson.prototype.setJsonSource = function (name, json) {
+    this.__setJson(PodsJson.SOURCE, name, json);
+};
+
+// sample json for declaration
+// { declaration: ""}
+PodsJson.prototype.setJsonDeclaration = function (name, json) {
+    this.__setJson(PodsJson.DECLARATION, name, json);
 };
 
 PodsJson.prototype.isDirty = function () {
diff --git a/bin/templates/scripts/cordova/lib/plugman/pluginHandlers.js b/bin/templates/scripts/cordova/lib/plugman/pluginHandlers.js
index 1f6920f..7894f46 100644
--- a/bin/templates/scripts/cordova/lib/plugman/pluginHandlers.js
+++ b/bin/templates/scripts/cordova/lib/plugman/pluginHandlers.js
@@ -129,16 +129,7 @@ var handlers = {
             if (!obj.custom) { // CB-9825 cocoapod integration for plugins
                 var keepFrameworks = keep_these_frameworks;
                 if (keepFrameworks.indexOf(src) < 0) {
-                    if (obj.type === 'podspec') {
-                        var podsJSON = require(path.join(project.projectDir, 'pods.json'));
-                        if (podsJSON[src]) {
-                            if (podsJSON[src].count > 1) {
-                                podsJSON[src].count = podsJSON[src].count - 1;
-                            } else {
-                                delete podsJSON[src];
-                            }
-                        }
-                    } else {
+                    if (obj.type !== 'podspec') {
                         // this should be refactored
                         project.frameworks[src] = project.frameworks[src] || 1;
                         project.frameworks[src]--;
diff --git a/tests/spec/unit/Api.spec.js b/tests/spec/unit/Api.spec.js
index 2e8db2d..32fca88 100644
--- a/tests/spec/unit/Api.spec.js
+++ b/tests/spec/unit/Api.spec.js
@@ -42,9 +42,17 @@ var Q = require('q');
 var FIXTURES = path.join(__dirname, 'fixtures');
 var iosProjectFixture = path.join(FIXTURES, 'ios-config-xml');
 
+function compareListWithoutOrder (list1, list2) {
+    expect(list1.sort()).toEqual(list2.sort());
+}
+
 describe('Platform Api', function () {
 
     describe('constructor', function () {
+        beforeEach(function () {
+            events.removeAllListeners();
+        });
+
         it('Test 001 : should throw if provided directory does not contain an xcodeproj file', function () {
             expect(function () { new Api('ios', path.join(FIXTURES, '..')); }).toThrow(); /* eslint no-new : 0 */
         });
@@ -130,6 +138,7 @@ describe('Platform Api', function () {
         var api;
         var projectRoot = iosProjectFixture;
         beforeEach(function () {
+            events.removeAllListeners();
             api = new Api('ios', projectRoot);
             spyOn(fs, 'readdirSync').and.returnValue([api.locations.xcodeProjDir]);
             spyOn(projectFile, 'parse').and.returnValue({
@@ -160,7 +169,8 @@ describe('Platform Api', function () {
         describe('addPlugin', function () {
             var my_plugin = {
                 getHeaderFiles: function () { return []; },
-                getFrameworks: function () {}
+                getFrameworks: function () {},
+                getPodSpecs: function () { return []; }
             };
             beforeEach(function () {
                 spyOn(PluginManager, 'get').and.returnValue({
@@ -199,7 +209,175 @@ describe('Platform Api', function () {
                             console.error(err);
                         }).done(done);
                 });
-
+            });
+            describe('adding pods since the plugin contained <podspecs>', function () {
+                var podsjson_mock;
+                var podfile_mock;
+                var my_pod_json = {
+                    declarations: {
+                        'use-frameworks': 'true',
+                        'inhibit_all_warnings!': 'true'
+                    },
+                    sources: {
+                        'https://github.com/sample/SampleSpecs.git': { source: 'https://github.com/sample/SampleSpecs.git' },
+                        'https://github.com/CocoaPods/Specs.git': { source: 'https://github.com/CocoaPods/Specs.git' }
+                    },
+                    libraries: {
+                        'AFNetworking': {
+                            name: 'AFNetworking',
+                            spec: '~> 3.2'
+                        },
+                        'Eureka': {
+                            name: 'Eureka',
+                            spec: '4.0',
+                            'swift-version': '4.1'
+                        },
+                        'HogeLib': {
+                            name: 'HogeLib',
+                            git: 'https://github.com/hoge/HogewLib.git',
+                            branch: 'develop'
+                        }
+                    }
+                };
+                beforeEach(function () {
+                    podsjson_mock = jasmine.createSpyObj('podsjson mock', ['getLibrary', 'getSource', 'getDeclaration',
+                        'incrementLibrary', 'incrementSource', 'incrementDeclaration', 'write',
+                        'setJsonLibrary', 'setJsonSource', 'setJsonDeclaration']);
+                    podfile_mock = jasmine.createSpyObj('podfile mock', ['isDirty', 'addSpec', 'addSource', 'addDeclaration', 'write', 'install']);
+                    spyOn(my_plugin, 'getFrameworks').and.returnValue([]);
+                    spyOn(my_plugin, 'getPodSpecs').and.returnValue([my_pod_json]);
+                    PodsJson_mod.PodsJson.and.callFake(function () {
+                        return podsjson_mock;
+                    });
+                    Podfile_mod.Podfile.and.callFake(function () {
+                        return podfile_mock;
+                    });
+                });
+                it('on a new declaration, it should add a new json to declarations', function (done) {
+                    api.addPlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                            expect(podsjson_mock.setJsonDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.setJsonDeclaration.calls.allArgs(),
+                                [['use_frameworks!', {declaration: 'use_frameworks!', count: 1}], ['inhibit_all_warnings!', {declaration: 'inhibit_all_warnings!', count: 1}]]);
+                            expect(podfile_mock.addDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podfile_mock.addDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                        }).fail(function (err) {
+                            fail('unexpected addPlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('should increment count in declarations if already exists', function (done) {
+                    podsjson_mock.getDeclaration.and.callFake(function (declaration) {
+                        if (declaration === 'use_frameworks!') {
+                            return {declaration: 'use_frameworks!', count: 1};
+                        }
+                        return null;
+                    });
+                    api.addPlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                            expect(podsjson_mock.incrementDeclaration).toHaveBeenCalledWith('use_frameworks!');
+                            expect(podsjson_mock.setJsonDeclaration.calls.count()).toEqual(1);
+                            compareListWithoutOrder(podsjson_mock.setJsonDeclaration.calls.allArgs(), [['inhibit_all_warnings!', {declaration: 'inhibit_all_warnings!', count: 1}]]);
+                            expect(podfile_mock.addDeclaration.calls.count()).toEqual(1);
+                            compareListWithoutOrder(podfile_mock.addDeclaration.calls.allArgs(), [['inhibit_all_warnings!']]);
+                        }).fail(function (err) {
+                            fail('unexpected addPlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('on a new source, it should add a new json to sources', function (done) {
+                    api.addPlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                            expect(podsjson_mock.setJsonSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.setJsonSource.calls.allArgs(), [
+                                ['https://github.com/sample/SampleSpecs.git', {source: 'https://github.com/sample/SampleSpecs.git', count: 1}],
+                                ['https://github.com/CocoaPods/Specs.git', {source: 'https://github.com/CocoaPods/Specs.git', count: 1}]
+                            ]);
+                            expect(podfile_mock.addSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podfile_mock.addSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                        }).fail(function (err) {
+                            fail('unexpected addPlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('should increment count in sources if already exists', function (done) {
+                    podsjson_mock.getSource.and.callFake(function (source) {
+                        if (source === 'https://github.com/CocoaPods/Specs.git') {
+                            return {source: 'https://github.com/CocoaPods/Specs.git', count: 1};
+                        }
+                        return null;
+                    });
+                    api.addPlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                            expect(podsjson_mock.incrementSource).toHaveBeenCalledWith('https://github.com/CocoaPods/Specs.git');
+                            expect(podsjson_mock.setJsonSource.calls.count()).toEqual(1);
+                            compareListWithoutOrder(podsjson_mock.setJsonSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git', {source: 'https://github.com/sample/SampleSpecs.git', count: 1}]]);
+                            expect(podfile_mock.addSource.calls.count()).toEqual(1);
+                            compareListWithoutOrder(podfile_mock.addSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git']]);
+                        }).fail(function (err) {
+                            fail('unexpected addPlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('on a new library, it should add a new json to library', function (done) {
+                    api.addPlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.getLibrary.calls.allArgs(), [
+                                ['AFNetworking'],
+                                ['Eureka'],
+                                ['HogeLib']
+                            ]);
+                            expect(podsjson_mock.setJsonLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.setJsonLibrary.calls.allArgs(), [
+                                ['AFNetworking', {name: 'AFNetworking', spec: '~> 3.2', count: 1}],
+                                ['Eureka', {name: 'Eureka', spec: '4.0', 'swift-version': '4.1', count: 1}],
+                                ['HogeLib', {name: 'HogeLib', git: 'https://github.com/hoge/HogewLib.git', branch: 'develop', count: 1}]
+                            ]);
+                            expect(podfile_mock.addSpec.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podfile_mock.addSpec.calls.allArgs(), [
+                                ['AFNetworking', {name: 'AFNetworking', spec: '~> 3.2', count: 1}],
+                                ['Eureka', {name: 'Eureka', spec: '4.0', 'swift-version': '4.1', count: 1}],
+                                ['HogeLib', {name: 'HogeLib', git: 'https://github.com/hoge/HogewLib.git', branch: 'develop', count: 1}]
+                            ]);
+                        }).fail(function (err) {
+                            fail('unexpected addPlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('should increment count in libraries if already exists', function (done) {
+                    podsjson_mock.getLibrary.and.callFake(function (library) {
+                        if (library === 'AFNetworking') {
+                            return {name: 'AFNetworking', spec: '~> 3.2', count: 1};
+                        }
+                        return null;
+                    });
+                    api.addPlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.getLibrary.calls.allArgs(), [
+                                ['AFNetworking'],
+                                ['Eureka'],
+                                ['HogeLib']
+                            ]);
+                            expect(podsjson_mock.incrementLibrary).toHaveBeenCalledWith('AFNetworking');
+                            expect(podsjson_mock.setJsonLibrary.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.setJsonLibrary.calls.allArgs(), [
+                                ['Eureka', {name: 'Eureka', spec: '4.0', 'swift-version': '4.1', count: 1}],
+                                ['HogeLib', {name: 'HogeLib', git: 'https://github.com/hoge/HogewLib.git', branch: 'develop', count: 1}]
+                            ]);
+                            expect(podfile_mock.addSpec.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podfile_mock.addSpec.calls.allArgs(), [
+                                ['Eureka', {name: 'Eureka', spec: '4.0', 'swift-version': '4.1', count: 1}],
+                                ['HogeLib', {name: 'HogeLib', git: 'https://github.com/hoge/HogewLib.git', branch: 'develop', count: 1}]
+                            ]);
+                        }).fail(function (err) {
+                            fail('unexpected addPlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
             });
             describe('with frameworks of `podspec` type', function () {
                 var podsjson_mock;
@@ -210,7 +388,7 @@ describe('Platform Api', function () {
                     spec: 'podspec!'
                 };
                 beforeEach(function () {
-                    podsjson_mock = jasmine.createSpyObj('podsjson mock', ['get', 'increment', 'write', 'setJson']);
+                    podsjson_mock = jasmine.createSpyObj('podsjson mock', ['getLibrary', 'incrementLibrary', 'write', 'setJsonLibrary']);
                     podfile_mock = jasmine.createSpyObj('podfile mock', ['isDirty', 'addSpec', 'write', 'install']);
                     spyOn(my_plugin, 'getFrameworks').and.returnValue([my_pod_json]);
                     PodsJson_mod.PodsJson.and.callFake(function () {
@@ -222,7 +400,7 @@ describe('Platform Api', function () {
                 });
                 // TODO: a little help with clearly labeling / describing the tests below? :(
                 it('should warn if Pods JSON contains name/src but differs in spec', function (done) {
-                    podsjson_mock.get.and.returnValue({
+                    podsjson_mock.getLibrary.and.returnValue({
                         spec: 'something different from ' + my_pod_json.spec
                     });
                     spyOn(events, 'emit');
@@ -235,12 +413,12 @@ describe('Platform Api', function () {
                         }).done(done);
                 });
                 it('should increment Pods JSON file if pod name/src already exists in file', function (done) {
-                    podsjson_mock.get.and.returnValue({
+                    podsjson_mock.getLibrary.and.returnValue({
                         spec: my_pod_json.spec
                     });
                     api.addPlugin(my_plugin)
                         .then(function () {
-                            expect(podsjson_mock.increment).toHaveBeenCalledWith('podsource!');
+                            expect(podsjson_mock.incrementLibrary).toHaveBeenCalledWith('podsource!');
                         }).fail(function (err) {
                             fail('unexpected addPlugin fail handler invoked');
                             console.error(err);
@@ -249,7 +427,7 @@ describe('Platform Api', function () {
                 it('on a new framework/pod name/src/key, it should add a new json to podsjson and add a new spec to podfile', function (done) {
                     api.addPlugin(my_plugin)
                         .then(function () {
-                            expect(podsjson_mock.setJson).toHaveBeenCalledWith(my_pod_json.src, jasmine.any(Object));
+                            expect(podsjson_mock.setJsonLibrary).toHaveBeenCalledWith(my_pod_json.src, jasmine.any(Object));
                             expect(podfile_mock.addSpec).toHaveBeenCalledWith(my_pod_json.src, my_pod_json.spec);
                         }).fail(function (err) {
                             fail('unexpected addPlugin fail handler invoked');
@@ -258,6 +436,7 @@ describe('Platform Api', function () {
                 });
                 it('should write out podfile and install if podfile was changed', function (done) {
                     podfile_mock.isDirty.and.returnValue(true);
+                    podfile_mock.install.and.returnValue({then: function () { }});
                     api.addPlugin(my_plugin)
                         .then(function () {
                             expect(podfile_mock.write).toHaveBeenCalled();
@@ -269,15 +448,15 @@ describe('Platform Api', function () {
                 });
                 it('if two frameworks with the same name are added, should honour the spec of the first-installed plugin', function (done) {
                     spyOn(events, 'emit');
-                    podsjson_mock.get.and.returnValue({
+                    podsjson_mock.getLibrary.and.returnValue({
                         spec: 'something different from ' + my_pod_json.spec
                     });
                     api.addPlugin(my_plugin)
                         .then(function () {
                             // Increment will non-destructively set the spec to keep it as it was...
-                            expect(podsjson_mock.increment).toHaveBeenCalledWith(my_pod_json.src);
+                            expect(podsjson_mock.incrementLibrary).toHaveBeenCalledWith(my_pod_json.src);
                             // ...whereas setJson would overwrite it completely.
-                            expect(podsjson_mock.setJson).not.toHaveBeenCalled();
+                            expect(podsjson_mock.setJsonLibrary).not.toHaveBeenCalled();
                         }).fail(function (err) {
                             fail('unexpected addPlugin fail handler invoked');
                             console.error(err);
@@ -285,5 +464,253 @@ describe('Platform Api', function () {
                 });
             });
         });
+        describe('removePlugin', function () {
+            var my_plugin = {
+                getHeaderFiles: function () { return []; },
+                getFrameworks: function () {},
+                getPodSpecs: function () { return []; }
+            };
+            beforeEach(function () {
+                spyOn(PluginManager, 'get').and.returnValue({
+                    removePlugin: function () { return Q(); }
+                });
+                spyOn(Podfile_mod, 'Podfile');
+                spyOn(PodsJson_mod, 'PodsJson');
+            });
+            describe('removing pods since the plugin contained <podspecs>', function () {
+                var podsjson_mock;
+                var podfile_mock;
+                var my_pod_json = {
+                    declarations: {
+                        'use-frameworks': 'true',
+                        'inhibit_all_warnings!': 'true'
+                    },
+                    sources: {
+                        'https://github.com/sample/SampleSpecs.git': { source: 'https://github.com/sample/SampleSpecs.git' },
+                        'https://github.com/CocoaPods/Specs.git': { source: 'https://github.com/CocoaPods/Specs.git' }
+                    },
+                    libraries: {
+                        'AFNetworking': {
+                            name: 'AFNetworking',
+                            spec: '~> 3.2'
+                        },
+                        'Eureka': {
+                            name: 'Eureka',
+                            spec: '4.0',
+                            'swift-version': '4.1'
+                        },
+                        'HogeLib': {
+                            name: 'HogeLib',
+                            git: 'https://github.com/hoge/HogewLib.git',
+                            branch: 'develop'
+                        }
+                    }
+                };
+                beforeEach(function () {
+                    podsjson_mock = jasmine.createSpyObj('podsjson mock', ['getLibrary', 'getSource', 'getDeclaration',
+                        'decrementLibrary', 'decrementSource', 'decrementDeclaration', 'write',
+                        'setJsonLibrary', 'setJsonSource', 'setJsonDeclaration']);
+                    podfile_mock = jasmine.createSpyObj('podfile mock', ['isDirty', 'removeSpec', 'removeSource', 'removeDeclaration', 'write', 'install']);
+                    spyOn(my_plugin, 'getFrameworks').and.returnValue([]);
+                    spyOn(my_plugin, 'getPodSpecs').and.returnValue([my_pod_json]);
+                    PodsJson_mod.PodsJson.and.callFake(function () {
+                        return podsjson_mock;
+                    });
+                    Podfile_mod.Podfile.and.callFake(function () {
+                        return podfile_mock;
+                    });
+                });
+                it('on a last declaration, it should remove a json from declarations', function (done) {
+                    var json1 = {declaration: 'use_frameworks!', count: 1};
+                    var json2 = {declaration: 'inhibit_all_warnings!', count: 1};
+                    podsjson_mock.getDeclaration.and.callFake(function (declaration) {
+                        if (declaration === 'use_frameworks!') {
+                            return json1;
+                        } else if (declaration === 'inhibit_all_warnings!') {
+                            return json2;
+                        }
+                        return null;
+                    });
+                    podsjson_mock.decrementDeclaration.and.callFake(function (declaration) {
+                        if (declaration === 'use_frameworks!') {
+                            json1.count--;
+                        } else if (declaration === 'inhibit_all_warnings!') {
+                            json2.count--;
+                        }
+                    });
+                    api.removePlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                            expect(podsjson_mock.decrementDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.decrementDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                            expect(podfile_mock.removeDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podfile_mock.removeDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                        }).fail(function (err) {
+                            fail('unexpected removePlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('should decrement count in declarations and does not remove if count > 1', function (done) {
+                    var json1 = {declaration: 'use_frameworks!', count: 2};
+                    var json2 = {declaration: 'inhibit_all_warnings!', count: 1};
+                    podsjson_mock.getDeclaration.and.callFake(function (declaration) {
+                        if (declaration === 'use_frameworks!') {
+                            return json1;
+                        } else if (declaration === 'inhibit_all_warnings!') {
+                            return json2;
+                        }
+                        return null;
+                    });
+                    podsjson_mock.decrementDeclaration.and.callFake(function (declaration) {
+                        if (declaration === 'use_frameworks!') {
+                            json1.count--;
+                        } else if (declaration === 'inhibit_all_warnings!') {
+                            json2.count--;
+                        }
+                    });
+                    api.removePlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                            expect(podsjson_mock.decrementDeclaration.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.decrementDeclaration.calls.allArgs(), [['use_frameworks!'], ['inhibit_all_warnings!']]);
+                            expect(podfile_mock.removeDeclaration.calls.count()).toEqual(1);
+                            compareListWithoutOrder(podfile_mock.removeDeclaration.calls.allArgs(), [['inhibit_all_warnings!']]);
+                        }).fail(function (err) {
+                            fail('unexpected removePlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('on a last source, it should remove a json from sources', function (done) {
+                    var json1 = { source: 'https://github.com/sample/SampleSpecs.git', count: 1 };
+                    var json2 = { source: 'https://github.com/CocoaPods/Specs.git', count: 1 };
+                    podsjson_mock.getSource.and.callFake(function (source) {
+                        if (source === 'https://github.com/sample/SampleSpecs.git') {
+                            return json1;
+                        } else if (source === 'https://github.com/CocoaPods/Specs.git') {
+                            return json2;
+                        }
+                        return null;
+                    });
+                    podsjson_mock.decrementSource.and.callFake(function (source) {
+                        if (source === 'https://github.com/sample/SampleSpecs.git') {
+                            json1.count--;
+                        } else if (source === 'https://github.com/CocoaPods/Specs.git') {
+                            json2.count--;
+                        }
+                    });
+                    api.removePlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                            expect(podsjson_mock.decrementSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.decrementSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                            expect(podfile_mock.removeSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podfile_mock.removeSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                        }).fail(function (err) {
+                            fail('unexpected removePlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('should decrement count in sources and does not remove if count > 1', function (done) {
+                    var json1 = { source: 'https://github.com/sample/SampleSpecs.git', count: 2 };
+                    var json2 = { source: 'https://github.com/CocoaPods/Specs.git', count: 1 };
+                    podsjson_mock.getSource.and.callFake(function (source) {
+                        if (source === 'https://github.com/sample/SampleSpecs.git') {
+                            return json1;
+                        } else if (source === 'https://github.com/CocoaPods/Specs.git') {
+                            return json2;
+                        }
+                        return null;
+                    });
+                    podsjson_mock.decrementSource.and.callFake(function (source) {
+                        if (source === 'https://github.com/sample/SampleSpecs.git') {
+                            json1.count--;
+                        } else if (source === 'https://github.com/CocoaPods/Specs.git') {
+                            json2.count--;
+                        }
+                    });
+                    api.removePlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.getSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                            expect(podsjson_mock.decrementSource.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podsjson_mock.decrementSource.calls.allArgs(), [['https://github.com/sample/SampleSpecs.git'], ['https://github.com/CocoaPods/Specs.git']]);
+                            expect(podfile_mock.removeSource.calls.count()).toEqual(1);
+                            compareListWithoutOrder(podfile_mock.removeSource.calls.allArgs(), [['https://github.com/CocoaPods/Specs.git']]);
+                        }).fail(function (err) {
+                            fail('unexpected removePlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('on a last library, it should remove a json from libraries', function (done) {
+                    var json1 = Object.assign({}, my_pod_json.libraries['AFNetworking'], { count: 1 });
+                    var json2 = Object.assign({}, my_pod_json.libraries['Eureka'], { count: 1 });
+                    var json3 = Object.assign({}, my_pod_json.libraries['HogeLib'], { count: 1 });
+                    podsjson_mock.getLibrary.and.callFake(function (name) {
+                        if (name === json1.name) {
+                            return json1;
+                        } else if (name === json2.name) {
+                            return json2;
+                        } else if (name === json3.name) {
+                            return json3;
+                        }
+                        return null;
+                    });
+                    podsjson_mock.decrementLibrary.and.callFake(function (name) {
+                        if (name === json1.name) {
+                            json1.count--;
+                        } else if (name === json2.name) {
+                            json2.count--;
+                        } else if (name === json3.name) {
+                            json3.count--;
+                        }
+                    });
+                    api.removePlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.getLibrary.calls.allArgs(), [[json1.name], [json2.name], [json3.name]]);
+                            expect(podsjson_mock.decrementLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.decrementLibrary.calls.allArgs(), [[json1.name], [json2.name], [json3.name]]);
+                            expect(podfile_mock.removeSpec.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podfile_mock.removeSpec.calls.allArgs(), [[json1.name], [json2.name], [json3.name]]);
+                        }).fail(function (err) {
+                            fail('unexpected removePlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+                it('should decrement count in libraries and does not remove if count > 1', function (done) {
+                    var json1 = Object.assign({}, my_pod_json.libraries['AFNetworking'], { count: 2 });
+                    var json2 = Object.assign({}, my_pod_json.libraries['Eureka'], { count: 1 });
+                    var json3 = Object.assign({}, my_pod_json.libraries['HogeLib'], { count: 1 });
+                    podsjson_mock.getLibrary.and.callFake(function (name) {
+                        if (name === json1.name) {
+                            return json1;
+                        } else if (name === json2.name) {
+                            return json2;
+                        } else if (name === json3.name) {
+                            return json3;
+                        }
+                        return null;
+                    });
+                    podsjson_mock.decrementLibrary.and.callFake(function (name) {
+                        if (name === json1.name) {
+                            json1.count--;
+                        } else if (name === json2.name) {
+                            json2.count--;
+                        } else if (name === json3.name) {
+                            json3.count--;
+                        }
+                    });
+                    api.removePlugin(my_plugin)
+                        .then(function () {
+                            expect(podsjson_mock.getLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.getLibrary.calls.allArgs(), [[json1.name], [json2.name], [json3.name]]);
+                            expect(podsjson_mock.decrementLibrary.calls.count()).toEqual(3);
+                            compareListWithoutOrder(podsjson_mock.decrementLibrary.calls.allArgs(), [[json1.name], [json2.name], [json3.name]]);
+                            expect(podfile_mock.removeSpec.calls.count()).toEqual(2);
+                            compareListWithoutOrder(podfile_mock.removeSpec.calls.allArgs(), [[json2.name], [json3.name]]);
+                        }).fail(function (err) {
+                            fail('unexpected removePlugin fail handler invoked :' + err);
+                        }).done(done);
+                });
+            });
+        });
     });
 });
diff --git a/tests/spec/unit/Podfile.spec.js b/tests/spec/unit/Podfile.spec.js
index 2d6e97c..080d111 100644
--- a/tests/spec/unit/Podfile.spec.js
+++ b/tests/spec/unit/Podfile.spec.js
@@ -100,6 +100,8 @@ describe('unit tests for Podfile module', function () {
             podfile.addSpec('Foo-Baz', '4.0');
             podfile.addSpec('Foo~Baz@!%@!%!', '5.0');
             podfile.addSpec('Bla', ':configurations => [\'Debug\', \'Beta\']');
+            podfile.addSpec('Bla2', {'configurations': 'Debug,Release'});
+            podfile.addSpec('Bla3', {'configurations': 'Debug, Release'});
 
             podfile.write();
 
@@ -112,12 +114,14 @@ describe('unit tests for Podfile module', function () {
             expect(newPodfile.existsSpec('Foo~Baz@!%@!%!')).toBe(true);
             expect(newPodfile.existsSpec('Bla')).toBe(true);
 
-            expect(newPodfile.getSpec('Foo')).toBe(podfile.getSpec('Foo'));
-            expect(newPodfile.getSpec('Bar')).toBe(podfile.getSpec('Bar'));
-            expect(newPodfile.getSpec('Baz')).toBe(podfile.getSpec('Baz'));
-            expect(newPodfile.getSpec('Foo-Baz')).toBe(podfile.getSpec('Foo-Baz'));
-            expect(newPodfile.getSpec('Foo~Baz@!%@!%!')).toBe(podfile.getSpec('Foo~Baz@!%@!%!'));
-            expect(newPodfile.getSpec('Bla')).toBe(podfile.getSpec('Bla'));
+            expect(newPodfile.getSpec('Foo')).toEqual(podfile.getSpec('Foo'));
+            expect(newPodfile.getSpec('Bar')).toEqual(podfile.getSpec('Bar'));
+            expect(newPodfile.getSpec('Baz')).toEqual(podfile.getSpec('Baz'));
+            expect(newPodfile.getSpec('Foo-Baz')).toEqual(podfile.getSpec('Foo-Baz'));
+            expect(newPodfile.getSpec('Foo~Baz@!%@!%!')).toEqual(podfile.getSpec('Foo~Baz@!%@!%!'));
+            expect(newPodfile.getSpec('Bla')).toEqual(podfile.getSpec('Bla'));
+            expect(newPodfile.getSpec('Bla2').options).toEqual(':configurations => [\'Debug\',\'Release\']');
+            expect(newPodfile.getSpec('Bla3').options).toEqual(':configurations => [\'Debug\',\'Release\']');
         });
 
         it('Test 009 : runs before_install to install xcconfig paths', function () {
diff --git a/tests/spec/unit/PodsJson.spec.js b/tests/spec/unit/PodsJson.spec.js
index 2543323..5667a41 100644
--- a/tests/spec/unit/PodsJson.spec.js
+++ b/tests/spec/unit/PodsJson.spec.js
@@ -17,6 +17,7 @@
        under the License.
 */
 
+var fs = require('fs');
 var path = require('path');
 var util = require('util');
 var CordovaError = require('cordova-common').CordovaError;
@@ -26,7 +27,13 @@ var fixturePodsJson = path.resolve(__dirname, 'fixtures', 'testProj', 'platforms
 
 // tests are nested in a describe to ensure clean up happens after all unit tests are run
 describe('unit tests for Podfile module', function () {
-    var podsjson = new PodsJson(fixturePodsJson);
+    var podsjson = null;
+    beforeEach(function () {
+        podsjson = new PodsJson(fixturePodsJson);
+    });
+    afterEach(function () {
+        podsjson.destroy();
+    });
 
     describe('tests', function () {
 
@@ -37,15 +44,15 @@ describe('unit tests for Podfile module', function () {
             }).toThrow(new CordovaError(util.format('PodsJson: The file at %s is not `%s`.', dummyPath, PodsJson.FILENAME)));
         });
 
-        it('Test 002 : sets and gets pod test', function () {
+        it('Test 002 : setsJson and gets pod test', function () {
             var val0 = {
                 name: 'Foo',
                 type: 'podspec',
                 spec: '1.0',
                 count: 1
             };
-            podsjson.set(val0.name, val0.type, val0.spec, val0.count);
-            var val1 = podsjson.get(val0.name);
+            podsjson.setJsonLibrary(val0.name, val0);
+            var val1 = podsjson.getLibrary(val0.name);
 
             expect(val1).toBeTruthy();
             expect(val1.name).toEqual(val0.name);
@@ -61,8 +68,8 @@ describe('unit tests for Podfile module', function () {
                 spec: '2.0',
                 count: 2
             };
-            podsjson.setJson(val0.name, val0);
-            var val1 = podsjson.get(val0.name);
+            podsjson.setJsonLibrary(val0.name, val0);
+            var val1 = podsjson.getLibrary(val0.name);
 
             expect(val1).toBeTruthy();
             expect(val1.name).toEqual(val0.name);
@@ -70,8 +77,8 @@ describe('unit tests for Podfile module', function () {
             expect(val1.spec).toEqual(val0.spec);
             expect(val1.count).toEqual(val0.count);
 
-            podsjson.remove(val0.name);
-            val1 = podsjson.get(val0.name);
+            podsjson.removeLibrary(val0.name);
+            val1 = podsjson.getLibrary(val0.name);
             expect(val1).toBeFalsy();
         });
 
@@ -82,12 +89,12 @@ describe('unit tests for Podfile module', function () {
                 spec: '3.0',
                 count: 3
             };
-            podsjson.setJson(val0.name, val0);
+            podsjson.setJsonLibrary(val0.name, val0);
             podsjson.clear();
 
-            expect(podsjson.get(val0.name)).toBeFalsy();
-            expect(podsjson.get('Foo')).toBeFalsy();
-            expect(podsjson.get('Bar')).toBeFalsy();
+            expect(podsjson.getLibrary(val0.name)).toBeFalsy();
+            expect(podsjson.getLibrary('Foo')).toBeFalsy();
+            expect(podsjson.getLibrary('Bar')).toBeFalsy();
         });
 
         it('Test 005 : isDirty tests', function () {
@@ -98,13 +105,13 @@ describe('unit tests for Podfile module', function () {
                 count: 1
             };
 
-            podsjson.setJson(val0.name, val0);
+            podsjson.setJsonLibrary(val0.name, val0);
             expect(podsjson.isDirty()).toBe(true);
 
             podsjson.write();
             expect(podsjson.isDirty()).toBe(false);
 
-            podsjson.remove(val0.name);
+            podsjson.removeLibrary(val0.name);
             expect(podsjson.isDirty()).toBe(true);
 
             podsjson.clear();
@@ -122,24 +129,24 @@ describe('unit tests for Podfile module', function () {
                 count: 4
             };
 
-            podsjson.setJson(val0.name, val0);
-            expect(podsjson.get(val0.name).count).toBe(4);
+            podsjson.setJsonLibrary(val0.name, val0);
+            expect(podsjson.getLibrary(val0.name).count).toBe(4);
 
-            podsjson.increment(val0.name);
-            expect(podsjson.get(val0.name).count).toBe(5);
+            podsjson.incrementLibrary(val0.name);
+            expect(podsjson.getLibrary(val0.name).count).toBe(5);
 
-            podsjson.decrement(val0.name);
-            expect(podsjson.get(val0.name).count).toBe(4);
-            podsjson.decrement(val0.name);
-            expect(podsjson.get(val0.name).count).toBe(3);
-            podsjson.decrement(val0.name);
-            expect(podsjson.get(val0.name).count).toBe(2);
-            podsjson.decrement(val0.name);
-            expect(podsjson.get(val0.name).count).toBe(1);
+            podsjson.decrementLibrary(val0.name);
+            expect(podsjson.getLibrary(val0.name).count).toBe(4);
+            podsjson.decrementLibrary(val0.name);
+            expect(podsjson.getLibrary(val0.name).count).toBe(3);
+            podsjson.decrementLibrary(val0.name);
+            expect(podsjson.getLibrary(val0.name).count).toBe(2);
+            podsjson.decrementLibrary(val0.name);
+            expect(podsjson.getLibrary(val0.name).count).toBe(1);
 
             // this next decrement takes it down to zero, where the pod will just be removed
-            podsjson.decrement(val0.name);
-            expect(podsjson.get(val0.name)).toBeFalsy();
+            podsjson.decrementLibrary(val0.name);
+            expect(podsjson.getLibrary(val0.name)).toBeFalsy();
         });
 
         it('Test 007 : writes pods to the pods.json', function () {
@@ -151,17 +158,17 @@ describe('unit tests for Podfile module', function () {
                 'Baz': { name: 'Baz', type: 'podspec', spec: '3.0', count: 3 }
             };
 
-            podsjson.setJson('Foo', vals.Foo);
-            podsjson.setJson('Bar', vals.Bar);
-            podsjson.setJson('Baz', vals.Baz);
+            podsjson.setJsonLibrary('Foo', vals.Foo);
+            podsjson.setJsonLibrary('Bar', vals.Bar);
+            podsjson.setJsonLibrary('Baz', vals.Baz);
 
             podsjson.write();
 
             // verify by reading it back in a new PodsJson
             var newPodsJson = new PodsJson(fixturePodsJson);
-            expect(newPodsJson.get('Foo')).toBeTruthy();
-            expect(newPodsJson.get('Bar')).toBeTruthy();
-            expect(newPodsJson.get('Baz')).toBeTruthy();
+            expect(newPodsJson.getLibrary('Foo')).toBeTruthy();
+            expect(newPodsJson.getLibrary('Bar')).toBeTruthy();
+            expect(newPodsJson.getLibrary('Baz')).toBeTruthy();
 
             function podEqual (a, b) {
                 return (
@@ -172,14 +179,76 @@ describe('unit tests for Podfile module', function () {
                 );
             }
 
-            expect(podEqual(podsjson.get('Foo'), newPodsJson.get('Foo'))).toBe(true);
-            expect(podEqual(podsjson.get('Bar'), newPodsJson.get('Bar'))).toBe(true);
-            expect(podEqual(podsjson.get('Baz'), newPodsJson.get('Baz'))).toBe(true);
+            expect(podEqual(podsjson.getLibrary('Foo'), newPodsJson.getLibrary('Foo'))).toBe(true);
+            expect(podEqual(podsjson.getLibrary('Bar'), newPodsJson.getLibrary('Bar'))).toBe(true);
+            expect(podEqual(podsjson.getLibrary('Baz'), newPodsJson.getLibrary('Baz'))).toBe(true);
         });
 
-    });
+        it('Test 008 : setJson, get, increment, decrement, remove and write for Declaration', function () {
+            var result = null;
+            var writeFileSyncSpy = spyOn(fs, 'writeFileSync');
+            writeFileSyncSpy.and.callFake(function (filepath, data, encode) {
+                result = data;
+            });
+            var json = {
+                declaration: 'use_frameworks!',
+                count: 1
+            };
+            var json2 = {
+                declaration: 'inhibit_all_warnings!',
+                count: 2
+            };
+            podsjson.setJsonDeclaration(json.declaration, json);
+            expect(podsjson.getDeclaration(json.declaration)).not.toBe(json);
+            expect(podsjson.getDeclaration(json.declaration)).toEqual(json);
+            podsjson.incrementDeclaration(json.declaration);
+            expect(podsjson.getDeclaration(json.declaration).count).toEqual(2);
+            podsjson.decrementDeclaration(json.declaration);
+            expect(podsjson.getDeclaration(json.declaration).count).toEqual(1);
+            podsjson.setJsonDeclaration(json2.declaration, json2);
+            expect(podsjson.getDeclaration(json.declaration)).toEqual(json);
+            expect(podsjson.getDeclaration(json2.declaration)).toEqual(json2);
+            podsjson.removeDeclaration(json.declaration);
+            expect(podsjson.getDeclaration(json.declaration)).toBeUndefined();
+            podsjson.write();
+            expect(writeFileSyncSpy).toHaveBeenCalled();
+            expect(JSON.parse(result).declarations[json2.declaration]).toEqual(json2);
+        });
+
+        it('Test 009 : setJson, get, increment, decrement, remove and write for Source', function () {
+            var result = null;
+            var writeFileSyncSpy = spyOn(fs, 'writeFileSync');
+            writeFileSyncSpy.and.callFake(function (filepath, data, encode) {
+                result = data;
+            });
+            var json = {
+                source: 'https://github.com/brightcove/BrightcoveSpecs.git',
+                count: 1
+            };
+            var json2 = {
+                source: 'https://github.com/CocoaPods/Specs.git',
+                count: 2
+            };
+            podsjson.setJsonSource(json.source, json);
+            expect(podsjson.getSource(json.source)).not.toBe(json);
+            expect(podsjson.getSource(json.source)).toEqual(json);
+            podsjson.incrementSource(json.source);
+            expect(podsjson.getSource(json.source).count).toEqual(2);
+            podsjson.decrementSource(json.source);
+            expect(podsjson.getSource(json.source).count).toEqual(1);
+            podsjson.setJsonSource(json2.source, json2);
+            expect(podsjson.getSource(json.source)).toEqual(json);
+            expect(podsjson.getSource(json2.source)).toEqual(json2);
+            podsjson.removeSource(json.source);
+            expect(podsjson.getSource(json.source)).toBeUndefined();
+            podsjson.write();
+            expect(writeFileSyncSpy).toHaveBeenCalled();
+            expect(JSON.parse(result).sources[json2.source]).toEqual(json2);
+        });
 
-    it('Test 008 : tear down', function () {
-        podsjson.destroy();
     });
+
+    // it('Test 008 : tear down', function () {
+    //     podsjson.destroy();
+    // });
 });
diff --git a/tests/spec/unit/prepare.spec.js b/tests/spec/unit/prepare.spec.js
index 033f050..2e7434c 100644
--- a/tests/spec/unit/prepare.spec.js
+++ b/tests/spec/unit/prepare.spec.js
@@ -18,7 +18,9 @@
  */
 
 'use strict';
-var fs = require('fs-extra');
+var fs = require('fs');
+var fse = require('fs-extra');
+
 var os = require('os');
 var path = require('path');
 var shell = require('shelljs');
@@ -769,7 +771,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsArbitraryLoadsInWebContent', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-arbitrary-loads-in-web-content="true" />' +
             '</widget>';
@@ -789,7 +791,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsArbitraryLoadsForMedia set (fixed allows-arbitrary-loads-for-media)', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-arbitrary-loads-for-media="true" />' +
             '</widget>';
@@ -808,7 +810,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsArbitraryLoadsForMedia not set (fixed allows-arbitrary-loads-for-media)', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-arbitrary-loads-for-media="false" />' +
             '</widget>';
@@ -827,7 +829,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsArbitraryLoadsForMedia set (deprecated allows-arbitrary-loads-in-media)', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-arbitrary-loads-in-media="true" />' +
             '</widget>';
@@ -846,7 +848,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsArbitraryLoadsForMedia not set (deprecated allows-arbitrary-loads-in-media)', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-arbitrary-loads-in-media="false" />' +
             '</widget>';
@@ -865,7 +867,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsLocalNetworking', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-local-networking="true" />' +
             '</widget>';
@@ -885,7 +887,7 @@ describe('prepare', function () {
 
         it('<access> - should handle wildcard, with NSAllowsArbitraryLoadsInWebContent, NSAllowsArbitraryLoadsForMedia, NSAllowsLocalNetworking', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="*" allows-arbitrary-loads-in-web-content="true" allows-arbitrary-loads-in-media="true" allows-local-networking="true" />' +
             '</widget>';
@@ -904,7 +906,7 @@ describe('prepare', function () {
         });
         it('<access> - sanity check - no wildcard but has NSAllowsArbitraryLoadsInWebContent, NSAllowsArbitraryLoadsForMedia, NSAllowsLocalNetworking', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<access origin="http://cordova.apache.org" allows-arbitrary-loads-in-web-content="true" allows-arbitrary-loads-in-media="true" allows-local-networking="true" />' +
             '</widget>';
@@ -1174,7 +1176,7 @@ describe('prepare', function () {
 
         it('<allow-navigation> - should handle wildcard', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<allow-navigation href="*" />' +
             '</widget>';
@@ -1194,7 +1196,7 @@ describe('prepare', function () {
 
         it('<allow-navigation> - sanity check - no wildcard but has NSAllowsArbitraryLoadsInWebContent, NSAllowsArbitraryLoadsForMedia, NSAllowsLocalNetworking', function (done) {
 
-            var readFile = spyOn(fs, 'readFileSync');
+            var readFile = spyOn(fse, 'readFileSync');
             var configXml = '<?xml version="1.0" encoding="UTF-8"?><widget 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"><name>SampleApp</name>' +
             '<allow-navigation href="http://cordova.apache.org" allows-arbitrary-loads-in-web-content="true" allows-arbitrary-loads-in-media="true" allows-local-networking="true" />' +
             '</widget>';


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org