You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2013/05/16 17:57:00 UTC

[01/20] git commit: giant refactor for dependencies, part one: changing the way install works.

Updated Branches:
  refs/heads/master 8b99d7c39 -> 0365cbf81


giant refactor for dependencies, part one: changing the way install works.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/01c3d7a6
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/01c3d7a6
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/01c3d7a6

Branch: refs/heads/master
Commit: 01c3d7a63ac50bc17f3343f53759368b98084450
Parents: 58cbc81
Author: Fil Maj <ma...@gmail.com>
Authored: Tue May 14 12:45:56 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 main.js                     |    2 +-
 package.json                |    3 +-
 src/install.js              |  183 +++++++++++++----------------
 src/platforms/android.js    |   65 ++---------
 src/platforms/blackberry.js |   55 ++-------
 src/platforms/common.js     |   13 ++
 src/platforms/ios.js        |  235 ++++++++++++++++++--------------------
 src/util/action-stack.js    |   50 ++++++++
 src/util/dependencies.js    |   56 ---------
 src/util/plugins.js         |    2 +
 10 files changed, 283 insertions(+), 381 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/main.js
----------------------------------------------------------------------
diff --git a/main.js b/main.js
index 8913d02..ca2e000 100755
--- a/main.js
+++ b/main.js
@@ -74,7 +74,7 @@ else {
             if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
         });
     }
-    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, cli_variables, cli_opts.www);
+    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, '.', cli_variables, cli_opts.www);
 }
 
 function printUsage() {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 2dfe157..6380287 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,8 @@
     "plist": "0.4.x",
     "bplist-parser": "0.0.x",
     "shelljs": "0.1.x",
-    "osenv": "0.0.x"
+    "osenv": "0.0.x",
+    "ncallbacks":"1.1.0"
   },
   "devDependencies": {
     "jasmine-node": "1.7.0"

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 37d789c..f73f5f3 100644
--- a/src/install.js
+++ b/src/install.js
@@ -1,12 +1,12 @@
 var path = require('path'),
     fs   = require('fs'),
     et   = require('elementtree'),
+    n    = require('ncallbacks'),
     config_changes = require('./util/config-changes'),
-    dependencies = require('./util/dependencies'),
+    action_stack = require('./util/action-stack'),
     platform_modules = require('./platforms');
 
-// TODO: is name necessary as a param ehre?
-module.exports = function installPlugin(platform, project_dir, name, plugins_dir, cli_variables, www_dir, callback) {
+module.exports = function installPlugin(platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, callback) {
     if (!platform_modules[platform]) {
         var err = new Error(platform + " not supported.");
         if (callback) {
@@ -16,12 +16,12 @@ module.exports = function installPlugin(platform, project_dir, name, plugins_dir
         else throw err;
     }
 
-    var plugin_dir = path.join(plugins_dir, name);
+    var plugin_dir = path.join(plugins_dir, id);
 
     // Check that the plugin has already been fetched.
     if (!fs.existsSync(plugin_dir)) {
         // if plugin doesnt exist, use fetch to get it.
-        require('../plugman').fetch(name, plugins_dir, false, '.', function(err, plugin_dir) {
+        require('../plugman').fetch(id, plugins_dir, false, subdir, function(err, plugin_dir) {
             if (err) {
                 callback(err);
             } else {
@@ -41,6 +41,21 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
       , filtered_variables = {};
     var name         = plugin_et.findall('name').text;
     var plugin_id    = plugin_et._root.attrib['id'];
+
+    // check if platform has plugin installed already.
+    var platform_config = config_changes.get_platform_json(plugins_dir, platform);
+    var plugin_basename = path.basename(plugin_dir);
+    var is_fully_installed = false;
+    Object.keys(platform_config.installed_plugins).forEach(function(installed_plugin_id) {
+        if (installed_plugin_id == plugin_id) {
+            is_fully_installed = true;
+        }
+    });
+    if (is_fully_installed) {
+        console.log('Plugin "' + plugin_id + '" already installed. Carry on.');
+        if (callback) callback();
+        return;
+    }
     
     // checking preferences, if certain variables are not provided, we should throw.
     prefs = plugin_et.findall('./preference') || [];
@@ -60,115 +75,79 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
         return;
     }
 
-    // check if platform has plugin fully installed or queued already.
-    var platform_config = config_changes.get_platform_json(plugins_dir, platform);
-    var plugin_basename = path.basename(plugin_dir);
-    if (platform_config.prepare_queue.installed.indexOf(plugin_basename) > -1) {
-        var err = new Error('plugin "' + plugin_basename + '" is already installed (but needs to be prepared)');
-        if (callback) callback(err);
-        else throw err;
-        return;
-    }
-    var is_fully_installed = false;
-    Object.keys(platform_config.installed_plugins).forEach(function(installed_plugin_id) {
-        if (installed_plugin_id == plugin_id) {
-            is_fully_installed = true;
-        }
-    });
-    if (is_fully_installed) {
-        var err = new Error('plugin "' + plugin_basename + '" (id: '+plugin_id+') is already installed');
-        if (callback) callback(err);
-        else throw err;
-        return;
+    // Check for dependencies, (co)recurse to install each one
+    var dependencies = plugin_et.findall('dependency');
+    if (dependencies && dependencies.length) {
+        var end = n(dependencies.length, function() {
+            handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, callback);
+        });
+        dependencies.forEach(function(dep) {
+            var dep_plugin_id = dep.attrib.id;
+            var dep_subdir = dep.attrib.subdir;
+            var dep_url = dep.attrib.url;
+            if (dep_subdir) {
+                dep_subdir = path.join.apply(null, dep_subdir.split('/'));
+            }
+
+            if (fs.existsSync(path.join(plugins_dir, dep_plugin_id))) {
+                console.log('Dependent plugin ' + dep.attrib.id + ' already fetched, using that version.');
+                module.exports(platform, project_dir, dep_plugin_id, plugins_dir, dep_subdir, filtered_variables, www_dir, end);
+            } else {
+                console.log('Dependent plugin ' + dep.attrib.id + ' not fetched, retrieving then installing.');
+                module.exports(platform, project_dir, dep_url, plugins_dir, dep_subdir, filtered_variables, www_dir, end);
+            }
+        });
+    } else {
+        handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, callback);
     }
+}
 
-    // We need to install this plugin, so install its dependencies first.
-    dependencies.installAll(platform, project_dir, path.basename(plugin_dir), plugins_dir, cli_variables, www_dir, callback);
+function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, callback) {
+    var handler = platform_modules[platform];
+    www_dir = www_dir || handler.www_dir(project_dir);
 
-    // TODO: if plugin does not have platform tag but has platform-agnostic config changes, should we queue it up?
     var platformTag = plugin_et.find('./platform[@name="'+platform+'"]');
-    if (!platformTag) {
-        // Either this plugin doesn't support this platform, or it's a JS-only plugin.
-        // Either way, return now.
-        // should call prepare probably!
-        finalizeInstall(project_dir, plugins_dir, platform, plugin_basename, filtered_variables, callback);
-        return;
-    }
+    var assets = plugin_et.findall('asset');
+    if (platformTag) {
+        var sourceFiles = platformTag.findall('./source-file'),
+            headerFiles = platformTag.findall('./header-file'),
+            resourceFiles = platformTag.findall('./resource-file'),
+            frameworks = platformTag.findall('./framework');
+        assets = assets.concat(platformTag.findall('./asset'));
 
-    // parse plugin.xml into transactions
-    var handler = platform_modules[platform];
-    var txs = [];
-    var sourceFiles = platformTag.findall('./source-file'),
-        headerFiles = platformTag.findall('./header-file'),
-        resourceFiles = platformTag.findall('./resource-file'),
-        assets = platformTag.findall('./asset'),
-        frameworks = platformTag.findall('./framework');
+        // queue up native stuff
+        sourceFiles && sourceFiles.forEach(function(source) {
+            action_stack.push(action_stack.createAction(handler["source-file"].install, [source, plugin_dir, project_dir], handler["source-file"].uninstall, [source, project_dir]));
+        });
 
-    assets = assets.concat(plugin_et.findall('./asset'));
+        headerFiles && headerFiles.forEach(function(header) {
+            action_stack.push(action_stack.createAction(handler["header-file"].install, [header, plugin_dir, project_dir], handler["header-file"].uninstall, [header, project_dir]));
+        });
 
-    // asset installation
-    var installedAssets = [];
-    var common = require('./platforms/common');
-    www_dir = www_dir || handler.www_dir(project_dir);
-    try {
-        for(var i = 0, j = assets.length ; i < j ; i++) {
-            var src = assets[i].attrib['src'],
-                target = assets[i].attrib['target'];
-            common.copyFile(plugin_dir, src, www_dir, target);
-            installedAssets.push(assets[i]);
-        }
-    } catch(err) {
-        var issue = 'asset installation failed\n'+err.stack+'\n';
-        try {
-            // removing assets and reverting install
-            for(var i = 0, j = installedAssets.length ; i < j ; i++) {
-               common.removeFile(www_dir, installedAssets[i].attrib.target);
-            }
-            common.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
-            issue += 'but successfully reverted\n';
-        } catch(err2) {
-            issue += 'and reversion failed :(\n' + err2.stack;
-        }
-        var error = new Error(issue);
-        if (callback) callback(error);
-        else throw error;
+        resourceFiles && resourceFiles.forEach(function(resource) {
+            action_stack.push(action_stack.createAction(handler["resource-file"].install, [resource, plugin_dir, project_dir], handler["resource-file"].uninstall, [resource, project_dir]));
+        });
+
+        frameworks && frameworks.forEach(function(framework) {
+            action_stack.push(action_stack.createAction(handler["framework"].install, [framework, plugin_dir, project_dir], handler["framework"].uninstall, [framework, project_dir]));
+        });
     }
-    txs = txs.concat(sourceFiles, headerFiles, resourceFiles, frameworks);
-    // pass platform-specific transactions into install
-    handler.install(txs, plugin_id, project_dir, plugin_dir, filtered_variables, function(err) {
+
+    // queue up asset installation
+    var common = require('./platforms/common');
+    assets && assets.forEach(function(asset) {
+        action_stack.push(action_stack.createAction(common.asset.install, [asset, plugin_dir, www_dir], common.asset.uninstall, [asset, www_dir, plugin_id]));
+    });
+
+    // run through the action stack
+    action_stack.process(function(err) {
         if (err) {
-            // FAIL
-            var issue = '';
-            try {
-                for(var i = 0, j = installedAssets.length ; i < j ; i++) {
-                   common.removeFile(www_dir, installedAssets[i].attrib.target);
-                }
-                common.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
-            } catch(err2) {
-                issue += 'Could not revert assets' + err2.stack + '\n';
-            }
-            if (err.transactions) {
-                handler.uninstall(err.transactions.executed, plugin_id, project_dir, plugin_dir, function(superr) {
-
-                    if (superr) {
-                        // Even reversion failed. super fail.
-                        issue += 'Install failed, then reversion of installation failed. Sorry :(. Instalation issue: ' + err.stack + ', reversion issue: ' + superr.stack;
-                    } else {
-                        issue += 'Install failed, plugin reversion successful so you should be good to go. Installation issue: ' + err.stack;
-                    }
-                    var error = new Error(issue);
-                    if (callback) callback(error);
-                    else throw error;
-                });
-            } else {
-                if (callback) callback(err);
-                else throw err;
-            }
+            console.error(err.message, err.stack);
+            console.error('Plugin installation failed :(');
         } else {
-
             // WIN!
             // Log out plugin INFO element contents in case additional install steps are necessary
-            var info = platformTag.findall('./info');
+            var info = (platformTag ? platformTag.findall('./info') : '');
             if(info.length) {
                 console.log(info[0].text);
             }

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/platforms/android.js
----------------------------------------------------------------------
diff --git a/src/platforms/android.js b/src/platforms/android.js
index cf62f93..cda9e72 100644
--- a/src/platforms/android.js
+++ b/src/platforms/android.js
@@ -19,19 +19,10 @@
 
 var fs = require('fs')  // use existsSync in 0.6.x
    , path = require('path')
-   , shell = require('shelljs')
    , common = require('./common')
-   , getConfigChanges = require(path.join(__dirname, '..', 'util', 'config-changes'))
-   , plugins_module = require(path.join(__dirname, '..', 'util', 'plugins'))
    , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'));
 
 module.exports = {
-    install:function(transactions, plugin_id, project_dir, plugin_dir, variables, callback) {
-        handlePlugin('install', plugin_id, transactions, project_dir, plugin_dir, variables, callback);
-    },
-    uninstall:function(transactions, plugin_id, project_dir, plugin_dir, callback) {
-        handlePlugin('uninstall', plugin_id, transactions, project_dir, plugin_dir, null, callback);
-    },
     www_dir:function(project_dir) {
         return path.join(project_dir, 'assets', 'www');
     },
@@ -42,51 +33,15 @@ module.exports = {
         var mDoc = xml_helpers.parseElementtreeSync(path.join(project_dir, 'AndroidManifest.xml'));
 
         return mDoc._root.attrib['package'];
-    }
-};
-
-function handlePlugin(action, plugin_id, txs, project_dir, plugin_dir, variables, callback) {
-    variables = variables || {};
-
-    // TODO: adding access tags?
-    // TODO: move this to prepare?
-    /*
-    var root = et.Element("config-file");
-    root.attrib['parent'] = '.';
-    plugin_et.findall('./access').forEach(function (tag) { 
-        root.append(tag);
-    });
-    */
-    var completed = [];
-    while(txs.length) {
-        var mod = txs.shift();
-        try {
-            switch(mod.tag.toLowerCase()) {
-                case 'source-file':
-                    var destFile = path.join(mod.attrib['target-dir'], path.basename(mod.attrib['src']));
-
-                    if (action == 'install') {
-                        common.copyFile(plugin_dir, mod.attrib['src'], project_dir, destFile);
-                    } else {
-                        common.deleteJava(project_dir, destFile);
-                    }
-                    break;
-                default:
-                    throw new Error('Unrecognized plugin.xml element/action in android installer: ' + mod.tag);
-                    break;
-            }
-        } catch(e) {
-            // propagate error up and provide completed tx log
-            e.transactions = {
-                executed:completed,
-                incomplete:txs.unshift(mod)
-            };
-            if (callback) callback(e);
-            else throw e;
-            return;
+    },
+    "source-file":{
+        install:function(source_el, plugin_dir, project_dir) {
+            var dest = path.join(source_el.attrib['target-dir'], path.basename(source_el.attrib['src']));
+            common.copyFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
+        },
+        uninstall:function(source_el, project_dir) {
+            var dest = path.join(source_el.attrib['target-dir'], path.basename(source_el.attrib['src']));
+            common.deleteJava(project_dir, dest);
         }
-        completed.push(mod);
     }
-
-    if (callback) callback();
-}
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/platforms/blackberry.js
----------------------------------------------------------------------
diff --git a/src/platforms/blackberry.js b/src/platforms/blackberry.js
index 9643a77..353ef92 100644
--- a/src/platforms/blackberry.js
+++ b/src/platforms/blackberry.js
@@ -19,59 +19,26 @@
 
 var fs = require('fs')  // use existsSync in 0.6.x
    , path = require('path')
-   , shell = require('shelljs')
-   , et = require('elementtree')
-   , getConfigChanges = require('../util/config-changes')
    , common = require('./common')
    , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'));
 
 module.exports = {
-    install:function(transactions, plugin_id, project_dir, plugin_dir, variables, callback) {
-        handlePlugin('install', plugin_id, transactions, project_dir, plugin_dir, variables, callback);
-    },
-    uninstall:function(transactions, plugin_id, project_dir, plugin_dir, callback) {
-        handlePlugin('uninstall', plugin_id, transactions, project_dir, plugin_dir, null, callback);
-    },
     www_dir:function(project_dir) {
         return path.join(project_dir, 'www');
     },
     package_name:function(project_dir) {
         var config_path = path.join(module.exports.www_dir(project_dir), 'config.xml');
-        var widget_doc = new et.ElementTree(et.XML(fs.readFileSync(config_path, 'utf-8')));
+        var widget_doc = xml_helpers.parseElementtreeSync(config_path);
         return widget_doc._root.attrib['id'];
-    }
-};
-
-function handlePlugin(action, plugin_id, txs, project_dir, plugin_dir, variables, callback) {
-    var completed = [];
-    while(txs.length) {
-        var mod = txs.shift();
-        try {
-            switch(mod.tag.toLowerCase()) {
-                case 'source-file':
-                    var destFile = path.join(mod.attrib['target-dir'], path.basename(mod.attrib['src']));
-
-                    if (action == 'install') {
-                        common.copyFile(plugin_dir, mod.attrib['src'], project_dir, destFile);
-                    } else {
-                        common.deleteJava(project_dir, destFile);
-                    }
-                    break;
-                default:
-                    throw new Error('Unrecognized plugin.xml element/action in blackberry installer: ' + mod.tag);
-                    break;
-            }
-        } catch(e) {
-            // propagate error up and provide completed tx log
-            e.transactions = {
-                executed:completed,
-                incomplete:txs.unshift(mod)
-            };
-            if (callback) callback(e);
-            else throw e;
-            return;
+    },
+    "source-file":{
+        install:function(source_el, plugin_dir, project_dir) {
+            var dest = path.join(source_el.attrib['target-dir'], path.basename(source_el.attrib['src']));
+            common.copyFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
+        },
+        uninstall:function(source_el, project_dir) {
+            var dest = path.join(source_el.attrib['target-dir'], path.basename(source_el.attrib['src']));
+            common.deleteJava(project_dir, dest);
         }
-        completed.push(mod);
     }
-    if (callback) callback();
-}
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/platforms/common.js
----------------------------------------------------------------------
diff --git a/src/platforms/common.js b/src/platforms/common.js
index 7d294b9..d4ee21d 100644
--- a/src/platforms/common.js
+++ b/src/platforms/common.js
@@ -55,5 +55,18 @@ module.exports = {
                 break;
             }
         }   
+    },
+    // handle <asset> elements
+    asset:{
+        install:function(asset_el, plugin_dir, www_dir) {
+            var src = asset_el.attrib.src;
+            var target = asset_el.attrib.target;
+            module.exports.copyFile(plugin_dir, src, www_dir, target);
+        },
+        uninstall:function(asset_el, www_dir, plugin_id) {
+            var target = asset_el.attrib.target;
+            module.exports.removeFile(www_dir, target);
+            module.exports.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
+        }
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/platforms/ios.js
----------------------------------------------------------------------
diff --git a/src/platforms/ios.js b/src/platforms/ios.js
index 4c012d7..c0187c5 100644
--- a/src/platforms/ios.js
+++ b/src/platforms/ios.js
@@ -20,44 +20,130 @@
 var path = require('path')
   , fs = require('../util/fs')  // use existsSync in 0.6.x
   , glob = require('glob')
-  , et = require('elementtree')
   , xcode = require('xcode')
   , plist = require('plist')
-  , bplist = require('bplist-parser')
-  , shell = require('shelljs')
-  , common = require('./common')
-  , xml_helpers = require(path.join(__dirname, '..', 'util', 'xml-helpers'))
-  , searchAndReplace = require(path.join(__dirname, '..', 'util', 'search-and-replace'))
-  , getConfigChanges = require(path.join(__dirname, '..', 'util', 'config-changes'));
+  , shell = require('shelljs');
 
 module.exports = {
-    install:function(transactions, plugin_id, project_dir, plugin_dir, variables, callback) {
-        handlePlugin('install', plugin_id, transactions, project_dir, plugin_dir, variables, callback);
-    },
-    uninstall:function(transactions, plugin_id, project_dir, plugin_dir, callback) {
-        handlePlugin('uninstall', plugin_id, transactions, project_dir, plugin_dir, null, callback);
-    },
     www_dir:function(project_dir) {
         return path.join(project_dir, 'www');
     },
     package_name:function(project_dir) {
         var plist_file = glob.sync(path.join(project_dir, '**', '*-Info.plist'))[0];
         return plist.parseFileSync(plist_file).CFBundleIdentifier;
+    },
+    "source-file":{
+        install:function(source_el, plugin_dir, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = source_el.attrib['src'];
+            var srcFile = path.resolve(plugin_dir, src);
+            var targetDir = path.resolve(project.plugins_dir, getRelativeDir(source_el));
+            var destFile = path.resolve(targetDir, path.basename(src));
+
+            if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <source-file>');
+            if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
+            project.xcode.addSourceFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));
+            shell.mkdir('-p', targetDir);
+            shell.cp(srcFile, destFile);
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        },
+        uninstall:function(source_el, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = source_el.attrib['src'];
+            var targetDir = path.resolve(project.plugins_dir, getRelativeDir(source_el));
+            var destFile = path.resolve(targetDir, path.basename(src));
+
+            project.xcode.removeSourceFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));
+            shell.rm('-rf', destFile);
+            
+            if(fs.existsSync(targetDir) && fs.readdirSync(targetDir).length>0){
+                shell.rm('-rf', targetDir); 
+            }
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        }
+    },
+    "header-file":{
+        install:function(header_el, plugin_dir, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = header_el.attrib['src'];
+            var srcFile = path.resolve(plugin_dir, src);
+            var targetDir = path.resolve(project.plugins_dir, getRelativeDir(header_el));
+            var destFile = path.resolve(targetDir, path.basename(src));
+            if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <header-file>');
+            if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
+            project.xcode.addHeaderFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));
+            shell.mkdir('-p', targetDir);
+            shell.cp(srcFile, destFile);
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        },
+        uninstall:function(header_el, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = header_el.attrib['src'];
+            var srcFile = path.resolve(plugin_dir, src);
+            var targetDir = path.resolve(project.plugins_dir, getRelativeDir(header_el));
+            var destFile = path.resolve(targetDir, path.basename(src));
+            project.xcode.removeHeaderFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));
+            shell.rm('-rf', destFile);
+            if(fs.existsSync(targetDir) && fs.readdirSync(targetDir).length>0){
+                shell.rm('-rf', targetDir); 
+            }
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        }
+    },
+    "resource-file":{
+        install:function(resource_el, plugin_dir, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = resource_el.attrib['src'],
+                srcFile = path.resolve(plugin_dir, src),
+                destFile = path.resolve(project.resources_dir, path.basename(src));
+            if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <resource-file>');
+            if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
+            project.xcode.addResourceFile(path.join('Resources', path.basename(src)));
+            shell.cp('-R', srcFile, resourcesDir);
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        },
+        uninstall:function(resource_el, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = resource_el.attrib['src'],
+                destFile = path.resolve(project.resources_dir, path.basename(src));
+            project.xcode.removeResourceFile(path.join('Resources', path.basename(src)));
+            shell.rm('-rf', destFile);
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        }
+    },
+    "framework":{
+        install:function(framework_el, plugin_dir, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = framework_el.attrib['src'],
+                weak = framework_el.attrib['weak'];
+            var opt = { weak: (weak == undefined || weak == null || weak != 'true' ? false : true ) };
+            project.xcode.addFramework(src, opt);
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        },
+        uninstall:function(framework_el, project_dir) {
+            var project = getIOSProjectFiles(project_dir);
+            var src = framework_el.attrib['src'];
+            project.xcode.removeFramework(src);
+            // write out xcodeproj file
+            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+        }
     }
 };
  
-function handlePlugin(action, plugin_id, txs, project_dir, plugin_dir, variables, callback) {
-    variables = variables || {};
-
+function getIOSProjectFiles(project_dir) {
     // grab and parse pbxproj
     // we don't want CordovaLib's xcode project
     var project_files = glob.sync(path.join(project_dir, '*.xcodeproj', 'project.pbxproj'));
     
     if (project_files.length === 0) {
-        var err = new Error("does not appear to be an xcode project (no xcode project file)");
-        if (callback) callback(err);
-        else throw err;
-        return;
+        throw new Error("does not appear to be an xcode project (no xcode project file)");
     }
     var pbxPath = project_files[0];
     var xcodeproj = xcode.project(pbxPath);
@@ -74,10 +160,7 @@ function handlePlugin(action, plugin_id, txs, project_dir, plugin_dir, variables
     });
 
     if (config_files.length === 0) {
-        var err = new Error("could not find PhoneGap/Cordova plist file, or config.xml file.");
-        if (callback) callback(err);
-        else throw err;
-        return;
+        throw new Error("could not find PhoneGap/Cordova plist file, or config.xml file.");
     }
 
     var config_file = config_files[0];
@@ -92,95 +175,12 @@ function handlePlugin(action, plugin_id, txs, project_dir, plugin_dir, variables
     // for certain config changes, we need to know if plugins-plist elements are present
     var plistEle = txs.filter(function(t) { return t.tag.toLowerCase() == 'plugins-plist'; })[0];
 
-    var completed = [];
-    while(txs.length) {
-        var mod = txs.shift();
-        try {
-            switch(mod.tag.toLowerCase()) {
-                case 'source-file':
-                    var src = mod.attrib['src'];
-                    var srcFile = path.resolve(plugin_dir, src);
-                    var targetDir = path.resolve(pluginsDir, getRelativeDir(mod));
-                    var destFile = path.resolve(targetDir, path.basename(src));
-                    if (action == 'install') {
-                        if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <source-file>');
-                        if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
-                        xcodeproj.addSourceFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
-                        shell.mkdir('-p', targetDir);
-                        shell.cp(srcFile, destFile);
-                    } else {
-                        xcodeproj.removeSourceFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
-                        shell.rm('-rf', destFile);
-                        
-                        if(fs.existsSync(targetDir) && fs.readdirSync(targetDir).length>0){
-                            shell.rm('-rf', targetDir); 
-                        }
-                    }
-                    break;
-                case 'header-file':
-                    var src = mod.attrib['src'];
-                    var srcFile = path.resolve(plugin_dir, src);
-                    var targetDir = path.resolve(pluginsDir, getRelativeDir(mod));
-                    var destFile = path.resolve(targetDir, path.basename(src));
-                    if (action == 'install') {     
-                        if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <header-file>');
-                        if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
-                        xcodeproj.addHeaderFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
-                        shell.mkdir('-p', targetDir);
-                        shell.cp(srcFile, destFile);
-                    } else {
-                        // TODO: doesnt preserve-dirs affect what the originally-added path to xcodeproj (see above) affect how we should call remove too?
-                        xcodeproj.removeHeaderFile(path.join('Plugins', path.relative(pluginsDir, destFile)));
-                        shell.rm('-rf', destFile);
-                        // TODO: again.. is this right? same as source-file
-                        shell.rm('-rf', targetDir);
-                    }
-                    break;
-                case 'resource-file':
-                    var src = mod.attrib['src'],
-                        srcFile = path.resolve(plugin_dir, src),
-                        destFile = path.resolve(resourcesDir, path.basename(src));
-
-                    if (action == 'install') {
-                        if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <resource-file>');
-                        if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
-                        xcodeproj.addResourceFile(path.join('Resources', path.basename(src)));
-                        shell.cp('-R', srcFile, resourcesDir);
-                    } else {
-                        xcodeproj.removeResourceFile(path.join('Resources', path.basename(src)));
-                        shell.rm('-rf', destFile);
-                    }
-                    break;
-                case 'framework':
-                    var src = mod.attrib['src'],
-                        weak = mod.attrib['weak'];
-                    if (action == 'install') {
-                        var opt = { weak: (weak == undefined || weak == null || weak != 'true' ? false : true ) };
-                        xcodeproj.addFramework(src, opt);
-                    } else {
-                        xcodeproj.removeFramework(src);
-                    }
-                    break;
-                default:
-                    throw new Error('Unrecognized plugin.xml element/action in ios installer: ' + mod.tag);
-                    break;
-            }
-        } catch(e) {
-            // propagate error up and provide completed tx log
-            e.transactions = {
-                executed:completed,
-                incomplete:txs.unshift(mod)
-            };
-            if (callback) callback(e);
-            else throw e;
-            return;
-        }
-        completed.push(mod);
-    }
-    // write out xcodeproj file
-    fs.writeFileSync(pbxPath, xcodeproj.writeSync());
-
-    if (callback) callback();
+    return {
+        plugins_dir:pluginsDir,
+        resources_dir:resourcesDir,
+        xcode:xcodeproj,
+        pbx:pbxPath
+    };
 }
 
 function getRelativeDir(file) {
@@ -191,12 +191,3 @@ function getRelativeDir(file) {
         return '';
     }
 }
-
-// determine if a plist file is binary
-function isBinaryPlist(filename) {
-    // I wish there was a synchronous way to read only the first 6 bytes of a
-    // file. This is wasteful :/ 
-    var buf = '' + fs.readFileSync(filename, 'utf8');
-    // binary plists start with a magic header, "bplist"
-    return buf.substring(0, 6) === 'bplist';
-}

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/util/action-stack.js
----------------------------------------------------------------------
diff --git a/src/util/action-stack.js b/src/util/action-stack.js
new file mode 100644
index 0000000..849bb54
--- /dev/null
+++ b/src/util/action-stack.js
@@ -0,0 +1,50 @@
+var stack = [];
+var completed = [];
+
+module.exports = {
+    createAction:function(handler, action_params, reverter, revert_params) {
+        return {
+            handler:{
+                run:handler,
+                params:action_params
+            },
+            reverter:{
+                run:reverter,
+                params:revert_params
+            }
+        };
+    },
+    push:function(tx) {
+        stack.push(tx);
+    },
+    process:function(callback) {
+        while(stack.length) {
+            var action = stack.shift();
+            var handler = action.handler.run;
+            var action_params = action.handler.params;
+            try {
+                handler.apply(null, action_params);
+            } catch(e) {
+                var incomplete = stack.unshift(action);
+                var issue = 'Install failed!\n';
+                // revert completed tasks
+                while(completed.length) {
+                    var undo = completed.shift();
+                    var revert = undo.reverter.run;
+                    var revert_params = undo.reverter.params;
+                    try {
+                        revert.apply(null, revert_params);
+                    } catch(err) {
+                        issue += 'A reversion action failed: ' + err.message + '\n';
+                    }
+                }
+                e.message = issue + e.message;
+                if (callback) callback(e);
+                else throw e;
+                return;
+            }
+            completed.push(action);
+        }
+        if (callback) callback();
+    }
+};

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/util/dependencies.js
----------------------------------------------------------------------
diff --git a/src/util/dependencies.js b/src/util/dependencies.js
deleted file mode 100644
index 9f7a594..0000000
--- a/src/util/dependencies.js
+++ /dev/null
@@ -1,56 +0,0 @@
-var install = require('../install'),
-    fetch   = require('../fetch'),
-    path    = require('path'),
-    fs      = require('fs'),
-    xml_helpers = require('./xml-helpers');
-
-exports.installAll = function(platform, project_dir, name, plugins_dir, cli_variables, www_dir, callback) {
-    // It should have been fetched already, so read the plugin.xml from plugin_dir
-    var plugin_dir = path.join(plugins_dir, name);
-    var xml = xml_helpers.parseElementtreeSync(path.join(plugin_dir, 'plugin.xml'));
-
-    var dependencies = xml.findall('dependency');
-
-    if (!dependencies || dependencies.length == 0) {
-        return;
-    }
-
-    function waitForAll(n) {
-        var count = n;
-        var errs = [];
-        return function(err) {
-            count--;
-            if (err) {
-                throw err;
-            }
-            if (count == 0) {
-                callback();
-            }
-        };
-    }
-
-    var dependencyCallback = waitForAll(dependencies.length);
-
-    dependencies && dependencies.forEach(function(dep) {
-        function doInstall(plugin_dir) {
-            // Call installation for this plugin after it gets fetched.
-            install(platform, project_dir, path.basename(plugin_dir), plugins_dir, cli_variables, www_dir, dependencyCallback);
-        }
-
-        // Check if this dependency is already there.
-        if (fs.existsSync(path.join(plugins_dir, dep.attrib.id))) {
-            console.log('Plugin ' + dep.attrib.id + ' already fetched, using that version.');
-            doInstall(path.join(plugins_dir, dep.attrib.id));
-        } else {
-            // Fetch it.
-            var subdir = dep.attrib.subdir;
-            if (subdir) {
-                subdir = path.join.apply(null, dep.attrib.subdir.split('/'));
-            }
-
-            console.log('Fetching dependency ' + dep.attrib.id);
-            fetch(dep.attrib.url, plugins_dir, false /* no link */, subdir, doInstall);
-        }
-    });
-};
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/01c3d7a6/src/util/plugins.js
----------------------------------------------------------------------
diff --git a/src/util/plugins.js b/src/util/plugins.js
index ff9cace..d5141cb 100644
--- a/src/util/plugins.js
+++ b/src/util/plugins.js
@@ -53,6 +53,8 @@ module.exports = {
                 var xml = xml_helpers.parseElementtreeSync(xml_file);
                 var plugin_id = xml.getroot().attrib.id;
 
+                // TODO: what if a plugin dependended on different subdirectories of the same plugin? this would fail.
+                // should probably copy over entire plugin git repo contents into plugins_dir and handle subdir seperately during install.
                 var plugin_dir = path.join(plugins_dir, plugin_id);
                 shell.cp('-R', path.join(tmp_dir, '*'), plugin_dir);
 


[19/20] git commit: tests fixed! woot.

Posted by fi...@apache.org.
tests fixed! woot.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/a77bfcc1
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/a77bfcc1
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/a77bfcc1

Branch: refs/heads/master
Commit: a77bfcc11096656df348e2f80452cb96ebd3afe8
Parents: 4cb155c
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 16:32:54 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:43 2013 -0700

----------------------------------------------------------------------
 spec/platforms/ios.spec.js       |  210 ++++++++++-----------------------
 spec/util/config-changes.spec.js |    2 +-
 2 files changed, 62 insertions(+), 150 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/a77bfcc1/spec/platforms/ios.spec.js
----------------------------------------------------------------------
diff --git a/spec/platforms/ios.spec.js b/spec/platforms/ios.spec.js
index 2dcee9c..cd22cf0 100644
--- a/spec/platforms/ios.spec.js
+++ b/spec/platforms/ios.spec.js
@@ -13,6 +13,7 @@ var ios = require('../../src/platforms/ios'),
     plugins_dir = path.join(temp, 'cordova', 'plugins'),
     ios_config_xml_project = path.join(__dirname, '..', 'projects', 'ios-config-xml', '*'),
     ios_plist_project = path.join(__dirname, '..', 'projects', 'ios-plist', '*'),
+    ios_project = path.join(ios_config_xml_project, '..'),
     xml_helpers = require('../../src/util/xml-helpers'),
     variableplugin = path.join(__dirname, '..', 'plugins', 'VariablePlugin'),
     faultyplugin = path.join(__dirname, '..', 'plugins', 'FaultyPlugin'),
@@ -61,6 +62,11 @@ platformTag = plugin_et.find('./platform[@name="ios"]');
 var plist_id = plugin_et._root.attrib['id'];
 var plist_only_els = platformTag.findall('./plugins-plist');
 
+shell.mkdir('-p', temp);
+shell.cp('-rf', ios_config_xml_project, temp);
+var proj_files = ios.parseIOSProjectFiles(temp);
+shell.rm('-rf', temp);
+
 function copyArray(arr) {
     return Array.prototype.slice.call(arr, 0);
 }
@@ -74,20 +80,22 @@ describe('ios project handler', function() {
         shell.rm('-rf', temp);
     });
 
-    it('should have an install function', function() {
-        expect(typeof ios.install).toEqual('function');
-    });
-    it('should have an uninstall function', function() {
-        expect(typeof ios.uninstall).toEqual('function');
+    describe('www_dir method', function() {
+        it('should return cordova-ios project www location using www_dir', function() {
+            expect(ios.www_dir('/')).toEqual('/www');
+        });
     });
-    it('should return cordova-ios project www location using www_dir', function() {
-        expect(ios.www_dir('/')).toEqual('/www');
+
+    describe('package_name method', function() {
+        it('should return the CFBundleIdentifier from the project\'s Info.plist file', function() {
+            expect(ios.package_name(ios_project)).toEqual('com.example.friendstring');
+        });
     });
 
-    describe('installation', function() {
+    describe('parseIOSProjectFiles method', function() {
         it('should throw if project is not an xcode project', function() {
             expect(function() {
-                ios.install([], 'someid', temp, plugins_dir, {});
+                ios.parseIOSProjectFiles(temp);
             }).toThrow('does not appear to be an xcode project (no xcode project file)');
         });
         it('should throw if project does not contain an appropriate PhoneGap/Cordova.plist file or config.xml file', function() {
@@ -95,10 +103,12 @@ describe('ios project handler', function() {
             shell.rm(path.join(temp, 'SampleApp', 'config.xml'));
 
             expect(function() {
-                ios.install([], 'someid', temp, plugins_dir, {});
+                ios.parseIOSProjectFiles(temp);
             }).toThrow('could not find PhoneGap/Cordova plist file, or config.xml file.');
         });
+    });
 
+    describe('installation', function() {
         describe('of <source-file> elements', function() {
             beforeEach(function() {
                 shell.cp('-rf', ios_config_xml_project, temp);
@@ -107,7 +117,7 @@ describe('ios project handler', function() {
             it('should throw if source-file src cannot be found', function() {
                 var source = copyArray(invalid_source);
                 expect(function() {
-                    ios.install(source, faulty_id, temp, faultyplugin, {});
+                    ios['source-file'].install(source[1], faultyplugin, temp, proj_files);
                 }).toThrow('cannot find "' + path.resolve(faultyplugin, 'src/ios/FaultyPluginCommand.m') + '" ios <source-file>');
             });
             it('should throw if source-file target already exists', function() {
@@ -116,51 +126,31 @@ describe('ios project handler', function() {
                 shell.mkdir('-p', path.dirname(target));
                 fs.writeFileSync(target, 'some bs', 'utf-8');
                 expect(function() {
-                    ios.install(source, dummy_id, temp, dummyplugin, {});
+                    ios['source-file'].install(source[0], dummyplugin, temp, proj_files);
                 }).toThrow('target destination "' + target + '" already exists');
             });
             it('should call into xcodeproj\'s addSourceFile appropriately when element has no target-dir', function() {
                 var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] == undefined});
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addSourceFile:spy
-                });
-                ios.install(source, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addSourceFile');
+                ios['source-file'].install(source[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'DummyPluginCommand.m'));
             });
             it('should call into xcodeproj\'s addSourceFile appropriately when element has a target-dir', function() {
                 var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] != undefined});
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addSourceFile:spy
-                });
-                ios.install(source, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addSourceFile');
+                ios['source-file'].install(source[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'targetDir', 'TargetDirTest.m'));
             });
             it('should cp the file to the right target location when element has no target-dir', function() {
                 var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] == undefined});
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addSourceFile:function() {}
-                });
                 var spy = spyOn(shell, 'cp');
-                ios.install(source, dummy_id, temp, dummyplugin, {});
+                ios['source-file'].install(source[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join(dummyplugin, 'src', 'ios', 'DummyPluginCommand.m'), path.join(temp, 'SampleApp', 'Plugins', 'DummyPluginCommand.m'));
             });
             it('should cp the file to the right target location when element has a target-dir', function() {
                 var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] != undefined});
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addSourceFile:function() {}
-                });
                 var spy = spyOn(shell, 'cp');
-                ios.install(source, dummy_id, temp, dummyplugin, {});
+                ios['source-file'].install(source[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join(dummyplugin, 'src', 'ios', 'TargetDirTest.m'), path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.m'));
             });
         });
@@ -173,7 +163,7 @@ describe('ios project handler', function() {
             it('should throw if header-file src cannot be found', function() {
                 var headers = copyArray(invalid_headers);
                 expect(function() {
-                    ios.install(headers, faulty_id, temp, faultyplugin, {});
+                    ios['header-file'].install(headers[1], faultyplugin, temp, proj_files);
                 }).toThrow('cannot find "' + path.resolve(faultyplugin, 'src/ios/FaultyPluginCommand.h') + '" ios <header-file>');
             });
             it('should throw if header-file target already exists', function() {
@@ -182,51 +172,31 @@ describe('ios project handler', function() {
                 shell.mkdir('-p', path.dirname(target));
                 fs.writeFileSync(target, 'some bs', 'utf-8');
                 expect(function() {
-                    ios.install(headers, dummy_id, temp, dummyplugin, {});
+                    ios['header-file'].install(headers[0], dummyplugin, temp, proj_files);
                 }).toThrow('target destination "' + target + '" already exists');
             });
             it('should call into xcodeproj\'s addHeaderFile appropriately when element has no target-dir', function() {
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] == undefined});
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addHeaderFile:spy
-                });
-                ios.install(headers, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addHeaderFile');
+                ios['header-file'].install(headers[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'DummyPluginCommand.h'));
             });
             it('should call into xcodeproj\'s addHeaderFile appropriately when element a target-dir', function() {
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined});
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addHeaderFile:spy
-                });
-                ios.install(headers, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addHeaderFile');
+                ios['header-file'].install(headers[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'targetDir', 'TargetDirTest.h'));
             });
             it('should cp the file to the right target location when element has no target-dir', function() {
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] == undefined});
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addHeaderFile:function() {}
-                });
                 var spy = spyOn(shell, 'cp');
-                ios.install(headers, dummy_id, temp, dummyplugin, {});
+                ios['header-file'].install(headers[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join(dummyplugin, 'src', 'ios', 'DummyPluginCommand.h'), path.join(temp, 'SampleApp', 'Plugins', 'DummyPluginCommand.h'));
             });
             it('should cp the file to the right target location when element has a target-dir', function() {
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined});
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addHeaderFile:function() {}
-                });
                 var spy = spyOn(shell, 'cp');
-                ios.install(headers, dummy_id, temp, dummyplugin, {});
+                ios['header-file'].install(headers[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join(dummyplugin, 'src', 'ios', 'TargetDirTest.h'), path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.h'));
             });
         });
@@ -238,7 +208,7 @@ describe('ios project handler', function() {
             it('should throw if resource-file src cannot be found', function() {
                 var resources = copyArray(invalid_resources);
                 expect(function() {
-                    ios.install(resources, faulty_id, temp, faultyplugin, {});
+                    ios['resource-file'].install(resources[0], faultyplugin, temp, proj_files);
                 }).toThrow('cannot find "' + path.resolve(faultyplugin, 'src/ios/IDontExist.bundle') + '" ios <resource-file>');
             });
             it('should throw if resource-file target already exists', function() {
@@ -247,29 +217,19 @@ describe('ios project handler', function() {
                 shell.mkdir('-p', path.dirname(target));
                 fs.writeFileSync(target, 'some bs', 'utf-8');
                 expect(function() {
-                    ios.install(resources, dummy_id, temp, dummyplugin, {});
+                    ios['resource-file'].install(resources[0], dummyplugin, temp, proj_files);
                 }).toThrow('target destination "' + target + '" already exists');
             });
             it('should call into xcodeproj\'s addResourceFile', function() {
                 var resources = copyArray(valid_resources);
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addResourceFile:spy
-                });
-                ios.install(resources, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addResourceFile');
+                ios['resource-file'].install(resources[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Resources', 'DummyPlugin.bundle'));
             });
             it('should cp the file to the right target location', function() {
                 var resources = copyArray(valid_resources);
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addResourceFile:function() {}
-                });
                 var spy = spyOn(shell, 'cp');
-                ios.install(resources, dummy_id, temp, dummyplugin, {});
+                ios['resource-file'].install(resources[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith('-R', path.join(dummyplugin, 'src', 'ios', 'DummyPlugin.bundle'), path.join(temp, 'SampleApp', 'Resources'));
             });
         });
@@ -280,55 +240,26 @@ describe('ios project handler', function() {
             });
             it('should call into xcodeproj\'s addFramework with weak false by default' ,function() {
                 var frameworks = copyArray(valid_frameworks).filter(function(f) { return f.attrib.weak == undefined; });
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addFramework:spy
-                });
-                ios.install(frameworks, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addFramework');
+                ios.framework.install(frameworks[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('src', 'ios', 'libsqlite3.dylib'), {weak:false});
             });
             it('should pass in whether the framework is weak or not to xcodeproj.addFramework', function() {
                 var frameworks = copyArray(valid_frameworks).filter(function(f) { return f.attrib.weak != undefined; });;
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    addFramework:spy
-                });
-                ios.install(frameworks, dummy_id, temp, dummyplugin, {});
+                var spy = spyOn(proj_files.xcode, 'addFramework');
+                ios.framework.install(frameworks[0], dummyplugin, temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('src', 'ios', 'libsqlite3.dylib'), {weak:true});
             });
         });
     });
 
     describe('uninstallation', function() {
-        it('should throw if project is not an xcode project', function() {
-            expect(function() {
-                ios.uninstall([], 'someid', temp, plugins_dir);
-            }).toThrow('does not appear to be an xcode project (no xcode project file)');
-        });
-        it('should throw if project does not contain an appropriate PhoneGap/Cordova.plist file or config.xml file', function() {
-            shell.cp('-rf', ios_config_xml_project, temp);
-            shell.rm(path.join(temp, 'SampleApp', 'config.xml'));
-            expect(function() {
-                ios.uninstall([], 'someid', temp, plugins_dir);
-            }).toThrow('could not find PhoneGap/Cordova plist file, or config.xml file.');
-        });
-
         describe('of <source-file> elements', function() {
             it('should call into xcodeproj\'s removeSourceFile appropriately when element has no target-dir', function(){
                 var source = copyArray(valid_source).filter(function(s) { return s.attrib['target-dir'] == undefined});
                 shell.cp('-rf', ios_config_xml_project, temp);
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    removeSourceFile:spy
-                });
-                
-                ios.uninstall(source, dummy_id, temp, dummyplugin);
+                var spy = spyOn(proj_files.xcode, 'removeSourceFile');
+                ios['source-file'].uninstall(source[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'DummyPluginCommand.m'));
             });
             it('should call into xcodeproj\'s removeSourceFile appropriately when element a target-dir', function(){
@@ -336,7 +267,7 @@ describe('ios project handler', function() {
                 shell.cp('-rf', ios_config_xml_project, temp);
                 
                 var spy = spyOn(shell, 'rm');
-                ios.uninstall(source, dummy_id, temp, dummyplugin);
+                ios['source-file'].uninstall(source[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.m'));
             });
             it('should rm the file from the right target location when element has no target-dir', function(){
@@ -344,7 +275,7 @@ describe('ios project handler', function() {
                 shell.cp('-rf', ios_config_xml_project, temp);
             
                 var spy = spyOn(shell, 'rm');
-                ios.uninstall(source, dummy_id, temp, dummyplugin);
+                ios['source-file'].uninstall(source[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'DummyPluginCommand.m'));
             });
             it('should rm the file from the right target location when element has a target-dir', function(){
@@ -352,7 +283,7 @@ describe('ios project handler', function() {
                 shell.cp('-rf', ios_config_xml_project, temp);                
                 var spy = spyOn(shell, 'rm');
                 
-                ios.uninstall(source, dummy_id, temp, dummyplugin);
+                ios['source-file'].uninstall(source[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.m'));
             });
         });
@@ -363,33 +294,24 @@ describe('ios project handler', function() {
             });
             it('should call into xcodeproj\'s removeHeaderFile appropriately when element has no target-dir', function(){
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] == undefined});
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    removeHeaderFile:spy
-                });
+                var spy = spyOn(proj_files.xcode, 'removeHeaderFile');
 
-                ios.uninstall(headers, dummy_id, temp, dummyplugin);
+                ios['header-file'].uninstall(headers[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'DummyPluginCommand.h'));
             });
             it('should call into xcodeproj\'s removeHeaderFile appropriately when element a target-dir', function(){
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined});
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    removeHeaderFile:spy
-                });
 
-                ios.uninstall(headers, dummy_id, temp, dummyplugin);
+                var spy = spyOn(proj_files.xcode, 'removeHeaderFile');
+
+                ios['header-file'].uninstall(headers[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Plugins', 'targetDir', 'TargetDirTest.h'));
             });
             it('should rm the file from the right target location', function(){
                 var headers = copyArray(valid_headers).filter(function(s) { return s.attrib['target-dir'] != undefined});
                 var spy = spyOn(shell, 'rm');
 
-                ios.uninstall(headers, dummy_id, temp, dummyplugin);
+                ios['header-file'].uninstall(headers[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Plugins', 'targetDir', 'TargetDirTest.h'));
             });
         });
@@ -400,21 +322,16 @@ describe('ios project handler', function() {
             });
             it('should call into xcodeproj\'s removeResourceFile', function(){
                 var resources = copyArray(valid_resources);
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    removeResourceFile:spy
-                });
+                var spy = spyOn(proj_files.xcode, 'removeResourceFile');
 
-                ios.uninstall(resources, dummy_id, temp, dummyplugin);
+                ios['resource-file'].uninstall(resources[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('Resources', 'DummyPlugin.bundle'));
             });
             it('should rm the file from the right target location', function(){
                 var resources = copyArray(valid_resources);
                 var spy = spyOn(shell, 'rm');
 
-                ios.uninstall(resources, dummy_id, temp, dummyplugin);
+                ios['resource-file'].uninstall(resources[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith('-rf', path.join(temp, 'SampleApp', 'Resources', 'DummyPlugin.bundle'));
             });
         });
@@ -425,14 +342,9 @@ describe('ios project handler', function() {
             });
             it('should call into xcodeproj\'s removeFramework' ,function() {
                 var frameworks = copyArray(valid_frameworks).filter(function(f) { return f.attrib.weak == undefined; });
-                var spy = jasmine.createSpy();
-                spyOn(xcode, 'project').andReturn({
-                    parseSync:function(){},
-                    writeSync:function(){},
-                    removeFramework:spy
-                });
+                var spy = spyOn(proj_files.xcode, 'removeFramework');
                 
-                ios.uninstall(frameworks, dummy_id, temp, dummyplugin);
+                ios.framework.uninstall(frameworks[0], temp, proj_files);
                 expect(spy).toHaveBeenCalledWith(path.join('src', 'ios', 'libsqlite3.dylib'));
             });
         });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/a77bfcc1/spec/util/config-changes.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/config-changes.spec.js b/spec/util/config-changes.spec.js
index 15d4618..de60063 100644
--- a/spec/util/config-changes.spec.js
+++ b/spec/util/config-changes.spec.js
@@ -295,7 +295,7 @@ describe('config-changes module', function() {
             it('should move successfully installed plugins from queue to installed plugins section, and include/retain vars if applicable', function() {
                 shell.cp('-rf', android_two_project, temp);
                 shell.cp('-rf', varplugin, plugins_dir);
-                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"hi"});
+                configChanges.add_installed_plugin_to_prepare_queue(plugins_dir, 'VariablePlugin', 'android', {"API_KEY":"hi"}, true);
 
                 configChanges.process(plugins_dir, temp, 'android');
 


[06/20] git commit: Make plugin cache directories be named after the plugin ID

Posted by fi...@apache.org.
Make plugin cache directories be named after the plugin ID

Previously they were named accidentally, after the git repo's or local
directory's basename.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/3aaa5a81
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/3aaa5a81
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/3aaa5a81

Branch: refs/heads/master
Commit: 3aaa5a815801a6f891fcb2ec243921c7ced4775c
Parents: 8b99d7c
Author: Braden Shepherdson <br...@gmail.com>
Authored: Thu May 9 12:18:58 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 spec/fetch.spec.js  |    2 +-
 spec/remove.spec.js |    6 +++---
 src/fetch.js        |    9 +++++++--
 src/util/plugins.js |   23 ++++++++++++++---------
 4 files changed, 25 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/3aaa5a81/spec/fetch.spec.js
----------------------------------------------------------------------
diff --git a/spec/fetch.spec.js b/spec/fetch.spec.js
index 8c07d44..b36ceab 100644
--- a/spec/fetch.spec.js
+++ b/spec/fetch.spec.js
@@ -8,7 +8,7 @@ var fetch   = require('../src/fetch'),
     plugins = require('../src/util/plugins');
 
 describe('fetch', function() {
-    var copied_plugin_path = path.join(temp,'ChildBrowser');
+    var copied_plugin_path = path.join(temp, 'com.phonegap.plugins.childbrowser');
 
     beforeEach(function() {
         shell.mkdir('-p', temp);

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/3aaa5a81/spec/remove.spec.js
----------------------------------------------------------------------
diff --git a/spec/remove.spec.js b/spec/remove.spec.js
index 444f420..4553b85 100644
--- a/spec/remove.spec.js
+++ b/spec/remove.spec.js
@@ -8,7 +8,7 @@ var remove  = require('../src/remove'),
     test_plugin = path.join(__dirname, 'plugins', 'ChildBrowser');
 
 describe('remove', function() {
-    var copied_plugin_path = path.join(temp,'ChildBrowser');
+    var copied_plugin_path = path.join(temp,'com.phonegap.plugins.childbrowser');
 
     beforeEach(function() {
         shell.mkdir('-p', temp);
@@ -19,12 +19,12 @@ describe('remove', function() {
 
     it('should remove symbolically-linked plugins', function() {
         fetch(test_plugin, temp, true);
-        remove('ChildBrowser', temp);
+        remove('com.phonegap.plugins.childbrowser', temp);
         expect(fs.readdirSync(temp).length).toEqual(0);
     });
     it('should remove non-linked plugins', function() {
         fetch(test_plugin, temp, false);
-        remove('ChildBrowser', temp);
+        remove('com.phonegap.plugins.childbrowser', temp);
         expect(fs.readdirSync(temp).length).toEqual(0);
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/3aaa5a81/src/fetch.js
----------------------------------------------------------------------
diff --git a/src/fetch.js b/src/fetch.js
index d5b4684..822f28d 100644
--- a/src/fetch.js
+++ b/src/fetch.js
@@ -1,6 +1,7 @@
 var shell   = require('shelljs'),
     fs      = require('fs'),
     plugins = require('./util/plugins'),
+    xml_helpers = require('./util/xml-helpers'),
     path    = require('path');
 
 module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, callback) {
@@ -18,13 +19,17 @@ module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, callback) {
         }
     } else {
         // Copy from the local filesystem.
-        var dest = path.join(plugins_dir, path.basename(plugin_dir));
+        // First, read the plugin.xml and grab the ID.
+        var xml = xml_helpers.parseElementtreeSync(path.join(plugin_dir, 'plugin.xml'));
+        var plugin_id = xml.getroot().attrib.id;
+
+        var dest = path.join(plugins_dir, plugin_id);
 
         shell.rm('-rf', dest);
         if (link) {
             fs.symlinkSync(path.resolve(plugin_dir), dest, 'dir');
         } else {
-            shell.cp('-R', plugin_dir, plugins_dir); // Yes, not dest.
+            shell.cp('-R', path.join(plugin_dir, '*'), dest);
         }
 
         if (callback) callback(null, dest);

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/3aaa5a81/src/util/plugins.js
----------------------------------------------------------------------
diff --git a/src/util/plugins.js b/src/util/plugins.js
index 3c55290..0a627cb 100644
--- a/src/util/plugins.js
+++ b/src/util/plugins.js
@@ -23,7 +23,9 @@ var http = require('http'),
     path = require('path'),
     fs = require('fs'),
     util = require('util'),
-    shell = require('shelljs');
+    shell = require('shelljs'),
+    xml_helpers = require('./xml-helpers'),
+    tmp_dir = path.join(path.sep, 'tmp', 'plugman-tmp');
 
 module.exports = {
     searchAndReplace:require('./search-and-replace'),
@@ -35,20 +37,23 @@ module.exports = {
             else throw err;
         }
 
-        var plugin_dir = path.join(plugins_dir, path.basename(plugin_git_url).replace(path.extname(plugin_git_url), ''));
+        shell.rm('-rf', tmp_dir);
 
-        // trash it if it already exists (something went wrong before probably)
-        // TODO: is this the correct behaviour?
-        if(fs.existsSync(plugin_dir)) {
-            shell.rm('-rf', plugin_dir);
-        }
-
-        shell.exec('git clone ' + plugin_git_url + ' ' + plugin_dir, {silent: true, async:true}, function(code, output) {
+        var cmd = util.format('git clone "%s" "%s"', plugin_git_url, tmp_dir);
+        shell.exec(cmd, {silent: true, async:true}, function(code, output) {
             if (code > 0) {
                 var err = new Error('failed to get the plugin via git from URL '+ plugin_git_url);
                 if (callback) callback(err)
                 else throw err;
             } else {
+                // Read the plugin.xml file and extract the plugin's ID.
+                var xml_file = path.join(tmp_dir, 'plugin.xml');
+                var xml = xml_helpers.parseElementtreeSync(xml_file);
+                var plugin_id = xml.getroot().attrib.id;
+
+                var plugin_dir = path.join(plugins_dir, plugin_id);
+                shell.cp('-R', path.join(tmp_dir, '*'), plugin_dir);
+
                 if (callback) callback(null, plugin_dir);
             }
         });


[16/20] git commit: axed old remove modules, fixed up android handler specs.

Posted by fi...@apache.org.
axed old remove modules, fixed up android handler specs.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/a99a9d31
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/a99a9d31
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/a99a9d31

Branch: refs/heads/master
Commit: a99a9d31236e96234f4bbbc9a18b5c83c06ed015
Parents: f025a2a
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 15:12:54 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:43 2013 -0700

----------------------------------------------------------------------
 plugman.js                     |    1 -
 spec/platforms/android.spec.js |   26 +++++++++++++-------------
 spec/remove.spec.js            |   30 ------------------------------
 src/remove.js                  |   14 --------------
 4 files changed, 13 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/a99a9d31/plugman.js
----------------------------------------------------------------------
diff --git a/plugman.js b/plugman.js
index 98e0cf4..a033545 100755
--- a/plugman.js
+++ b/plugman.js
@@ -21,7 +21,6 @@
 module.exports = {
     install:  require('./src/install'),
     uninstall:require('./src/uninstall'),
-    remove:   require('./src/remove'),
     fetch:    require('./src/fetch'),
     prepare:  require('./src/prepare')
 };

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/a99a9d31/spec/platforms/android.spec.js
----------------------------------------------------------------------
diff --git a/spec/platforms/android.spec.js b/spec/platforms/android.spec.js
index 57ed4c9..f65caa3 100644
--- a/spec/platforms/android.spec.js
+++ b/spec/platforms/android.spec.js
@@ -47,14 +47,15 @@ function copyArray(arr) {
 }
 
 describe('android project handler', function() {
-    it('should have an install function', function() {
-        expect(typeof android.install).toEqual('function');
-    });
-    it('should have an uninstall function', function() {
-        expect(typeof android.uninstall).toEqual('function');
+    describe('www_dir method', function() {
+        it('should return cordova-android project www location using www_dir', function() {
+            expect(android.www_dir('/')).toEqual('/assets/www');
+        });
     });
-    it('should return cordova-android project www location using www_dir', function() {
-        expect(android.www_dir('/')).toEqual('/assets/www');
+    describe('package_name method', function() {
+        it('should return an android project\'s proper package name', function() {
+            expect(android.package_name(path.join(android_one_project, '..'))).toEqual('com.alunny.childapp');
+        });
     });
 
     describe('installation', function() {
@@ -72,13 +73,13 @@ describe('android project handler', function() {
             it('should copy stuff from one location to another by calling common.copyFile', function() {
                 var source = copyArray(valid_source);
                 var s = spyOn(common, 'copyFile');
-                android.install(source, dummy_id, temp, dummyplugin, {});
+                android['source-file'].install(source[0], dummyplugin, temp); 
                 expect(s).toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin.java', temp, 'src/com/phonegap/plugins/dummyplugin/DummyPlugin.java');
             });
             it('should throw if source file cannot be found', function() {
                 var source = copyArray(invalid_source);
                 expect(function() {
-                    android.install(source, faulty_id, temp, faultyplugin, {});
+                    android['source-file'].install(source[0], faultyplugin, temp); 
                 }).toThrow('"' + path.resolve(faultyplugin, 'src/android/NotHere.java') + '" not found!');
             });
             it('should throw if target file already exists', function() {
@@ -90,7 +91,7 @@ describe('android project handler', function() {
 
                 var source = copyArray(valid_source);
                 expect(function() {
-                    android.install(source, dummy_id, temp, dummyplugin, {});
+                    android['source-file'].install(source[0], dummyplugin, temp); 
                 }).toThrow('"' + target + '" already exists!');
             });
         });
@@ -101,7 +102,6 @@ describe('android project handler', function() {
             shell.mkdir('-p', temp);
             shell.mkdir('-p', plugins_dir);
             shell.cp('-rf', android_two_project, temp);
-            shell.cp('-rf', dummyplugin, plugins_dir);
         });
         afterEach(function() {
             shell.rm('-rf', temp);
@@ -109,9 +109,9 @@ describe('android project handler', function() {
         describe('of <source-file> elements', function() {
             it('should remove stuff by calling common.deleteJava', function(done) {
                 var s = spyOn(common, 'deleteJava');
-                install('android', temp, 'DummyPlugin', plugins_dir, {}, undefined, function() {
+                install('android', temp, dummyplugin, plugins_dir, '.', {}, undefined, function() {
                     var source = copyArray(valid_source);
-                    android.uninstall(source, dummy_id, temp, path.join(plugins_dir, 'DummyPlugin'));
+                    android['source-file'].uninstall(source[0], temp);
                     expect(s).toHaveBeenCalledWith(temp, 'src/com/phonegap/plugins/dummyplugin/DummyPlugin.java');
                     done();
                 });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/a99a9d31/spec/remove.spec.js
----------------------------------------------------------------------
diff --git a/spec/remove.spec.js b/spec/remove.spec.js
deleted file mode 100644
index 4553b85..0000000
--- a/spec/remove.spec.js
+++ /dev/null
@@ -1,30 +0,0 @@
-var remove  = require('../src/remove'),
-    fetch   = require('../src/fetch'),
-    fs      = require('fs'),
-    os      = require('osenv'),
-    path    = require('path'),
-    shell   = require('shelljs'),
-    temp    = path.join(os.tmpdir(), 'plugman'),
-    test_plugin = path.join(__dirname, 'plugins', 'ChildBrowser');
-
-describe('remove', function() {
-    var copied_plugin_path = path.join(temp,'com.phonegap.plugins.childbrowser');
-
-    beforeEach(function() {
-        shell.mkdir('-p', temp);
-    });
-    afterEach(function() {
-        try{shell.rm('-rf', temp);}catch(e){}
-    });
-
-    it('should remove symbolically-linked plugins', function() {
-        fetch(test_plugin, temp, true);
-        remove('com.phonegap.plugins.childbrowser', temp);
-        expect(fs.readdirSync(temp).length).toEqual(0);
-    });
-    it('should remove non-linked plugins', function() {
-        fetch(test_plugin, temp, false);
-        remove('com.phonegap.plugins.childbrowser', temp);
-        expect(fs.readdirSync(temp).length).toEqual(0);
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/a99a9d31/src/remove.js
----------------------------------------------------------------------
diff --git a/src/remove.js b/src/remove.js
deleted file mode 100644
index ada9afa..0000000
--- a/src/remove.js
+++ /dev/null
@@ -1,14 +0,0 @@
-var shell = require('shelljs'),
-    fs    = require('fs'),
-    path  = require('path');
-
-module.exports = function removePlugin(name, plugins_dir) {
-    var target = path.join(plugins_dir, name);
-    var stat = fs.lstatSync(target);
-
-    if (stat.isSymbolicLink()) {
-        fs.unlinkSync(target);
-    } else {
-        shell.rm('-rf', target);
-    }
-};


[10/20] git commit: Add git ref support to fetch. Not used anywhere yet.

Posted by fi...@apache.org.
Add git ref support to fetch. Not used anywhere yet.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/2e5f15f5
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/2e5f15f5
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/2e5f15f5

Branch: refs/heads/master
Commit: 2e5f15f57c9bfc216b2eac986c38f51e0229050d
Parents: 01c3d7a
Author: Braden Shepherdson <br...@gmail.com>
Authored: Tue May 14 17:10:50 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:42 2013 -0700

----------------------------------------------------------------------
 spec/fetch.spec.js  |   10 +++++++++-
 src/fetch.js        |    4 ++--
 src/install.js      |    3 ++-
 src/util/plugins.js |   12 +++++++++++-
 4 files changed, 24 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/2e5f15f5/spec/fetch.spec.js
----------------------------------------------------------------------
diff --git a/spec/fetch.spec.js b/spec/fetch.spec.js
index 8f7c678..79afaa4 100644
--- a/spec/fetch.spec.js
+++ b/spec/fetch.spec.js
@@ -42,7 +42,15 @@ describe('fetch', function() {
             var url = "https://github.com/bobeast/GAPlugin.git";
             var dir = 'fakeSubDir';
             fetch(url, temp, false, dir);
-            expect(s).toHaveBeenCalledWith(url, temp, dir, undefined);
+            expect(s).toHaveBeenCalledWith(url, temp, dir, undefined, undefined);
+        });
+        it('should call clonePluginGitRepo with subdir and git ref if applicable', function() {
+            var s = spyOn(plugins, 'clonePluginGitRepo');
+            var url = "https://github.com/bobeast/GAPlugin.git";
+            var dir = 'fakeSubDir';
+            var ref = 'fakeGitRef';
+            fetch(url, temp, false, dir, ref);
+            expect(s).toHaveBeenCalledWith(url, temp, dir, ref, undefined);
         });
         it('should throw if used with url and `link` param', function() {
             expect(function() {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/2e5f15f5/src/fetch.js
----------------------------------------------------------------------
diff --git a/src/fetch.js b/src/fetch.js
index 73bf4e2..a222af6 100644
--- a/src/fetch.js
+++ b/src/fetch.js
@@ -4,7 +4,7 @@ var shell   = require('shelljs'),
     xml_helpers = require('./util/xml-helpers'),
     path    = require('path');
 
-module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, callback) {
+module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, git_ref, callback) {
     // Ensure the containing directory exists.
     shell.mkdir('-p', plugins_dir);
 
@@ -17,7 +17,7 @@ module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, cal
             if (callback) callback(err);
             else throw err;
         } else {
-            plugins.clonePluginGitRepo(plugin_dir, plugins_dir, subdir, callback);
+            plugins.clonePluginGitRepo(plugin_dir, plugins_dir, subdir, git_ref, callback);
         }
     } else {
         // Copy from the local filesystem.

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/2e5f15f5/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index f73f5f3..74c8e41 100644
--- a/src/install.js
+++ b/src/install.js
@@ -21,7 +21,8 @@ module.exports = function installPlugin(platform, project_dir, id, plugins_dir,
     // Check that the plugin has already been fetched.
     if (!fs.existsSync(plugin_dir)) {
         // if plugin doesnt exist, use fetch to get it.
-        require('../plugman').fetch(id, plugins_dir, false, subdir, function(err, plugin_dir) {
+        // TODO: Actual value for git_ref.
+        require('../plugman').fetch(id, plugins_dir, false, '.', undefined /* git_ref */, function(err, plugin_dir) {
             if (err) {
                 callback(err);
             } else {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/2e5f15f5/src/util/plugins.js
----------------------------------------------------------------------
diff --git a/src/util/plugins.js b/src/util/plugins.js
index d5141cb..f1b3f0b 100644
--- a/src/util/plugins.js
+++ b/src/util/plugins.js
@@ -30,7 +30,7 @@ var http = require('http'),
 module.exports = {
     searchAndReplace:require('./search-and-replace'),
     // Fetches plugin information from remote server
-    clonePluginGitRepo:function(plugin_git_url, plugins_dir, subdir, callback) {
+    clonePluginGitRepo:function(plugin_git_url, plugins_dir, subdir, git_ref, callback) {
         if(!shell.which('git')) {
             var err = new Error('git command line is not installed');
             if (callback) callback(err);
@@ -46,6 +46,16 @@ module.exports = {
                 if (callback) callback(err)
                 else throw err;
             } else {
+                // Check out the specified revision, if provided.
+                if (git_ref) {
+                    var result = shell.exec(util.format('git checkout "%s"', git_ref), { silent: true });
+                    if (result.code > 0) {
+                        var err = new Error('failed to checkout git ref "' + git_ref + '"');
+                        if (callback) callback(err);
+                        else throw err;
+                    }
+                }
+
                 // Read the plugin.xml file and extract the plugin's ID.
                 tmp_dir = path.join(tmp_dir, subdir);
                 // TODO: what if plugin.xml does not exist?


[12/20] git commit: differentiating between top-level and dependent plugins. uninstall should work for the single plugin case (still need to fire off dependent plugin danglin uninstalls).

Posted by fi...@apache.org.
differentiating between top-level and dependent plugins. uninstall should work for the single plugin case (still need to fire off dependent plugin danglin uninstalls).


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/be96c196
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/be96c196
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/be96c196

Branch: refs/heads/master
Commit: be96c196d051ba1a2e3707151a3496e9339e9b59
Parents: adf3e1e
Author: Fil Maj <ma...@gmail.com>
Authored: Tue May 14 18:52:54 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:42 2013 -0700

----------------------------------------------------------------------
 main.js                    |    4 +-
 package.json               |    4 +-
 src/install.js             |   53 +++++++-------
 src/platforms/ios.js       |    1 -
 src/uninstall.js           |  156 +++++++++++++++++++--------------------
 src/util/action-stack.js   |    2 +-
 src/util/config-changes.js |   27 +++++--
 src/util/dependencies.js   |   34 +++++++++
 8 files changed, 161 insertions(+), 120 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/main.js
----------------------------------------------------------------------
diff --git a/main.js b/main.js
index ca2e000..414cf18 100755
--- a/main.js
+++ b/main.js
@@ -63,7 +63,7 @@ else if (!cli_opts.platform || !cli_opts.project || !cli_opts.plugin) {
     printUsage();
 }
 else if (cli_opts.uninstall) {
-    plugman.uninstall(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, cli_opts.www);
+    plugman.uninstall(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, {}, cli_opts.www, true /* is top level? */);
 }
 else {
     var cli_variables = {}
@@ -74,7 +74,7 @@ else {
             if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
         });
     }
-    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, '.', cli_variables, cli_opts.www);
+    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, '.', cli_variables, cli_opts.www, true /* is top level? */);
 }
 
 function printUsage() {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 6380287..73ab275 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,9 @@
     "bplist-parser": "0.0.x",
     "shelljs": "0.1.x",
     "osenv": "0.0.x",
-    "ncallbacks":"1.1.0"
+    "ncallbacks":"1.1.0",
+    "underscore":"1.4.4",
+    "dep-graph":"1.1.0"
   },
   "devDependencies": {
     "jasmine-node": "1.7.0"

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index cd5652f..24f012d 100644
--- a/src/install.js
+++ b/src/install.js
@@ -6,14 +6,12 @@ var path = require('path'),
     action_stack = require('./util/action-stack'),
     platform_modules = require('./platforms');
 
-module.exports = function installPlugin(platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, callback) {
+module.exports = function installPlugin(platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, is_top_level, callback) {
     if (!platform_modules[platform]) {
         var err = new Error(platform + " not supported.");
-        if (callback) {
-            callback(err);
-            return;
-        }
+        if (callback) callback(err);
         else throw err;
+        return;
     }
 
     var plugin_dir = path.join(plugins_dir, id);
@@ -27,15 +25,15 @@ module.exports = function installPlugin(platform, project_dir, id, plugins_dir,
                 callback(err);
             } else {
                 // update ref to plugin_dir after successful fetch, via fetch callback
-                runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, callback);
+                runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
             }
         });
     } else {
-        runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, callback);
+        runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
     }
 };
 
-function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, callback) {
+function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
     var xml_path     = path.join(plugin_dir, 'plugin.xml')
       , xml_text     = fs.readFileSync(xml_path, 'utf-8')
       , plugin_et    = new et.ElementTree(et.XML(xml_text))
@@ -46,14 +44,19 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
     // check if platform has plugin installed already.
     var platform_config = config_changes.get_platform_json(plugins_dir, platform);
     var plugin_basename = path.basename(plugin_dir);
-    var is_fully_installed = false;
+    var is_installed = false;
     Object.keys(platform_config.installed_plugins).forEach(function(installed_plugin_id) {
         if (installed_plugin_id == plugin_id) {
-            is_fully_installed = true;
+            is_installed = true;
+        }
+    });
+    Object.keys(platform_config.dependent_plugins).forEach(function(installed_plugin_id) {
+        if (installed_plugin_id == plugin_id) {
+            is_installed = true;
         }
     });
-    if (is_fully_installed) {
-        console.log('Plugin "' + plugin_id + '" already installed. Carry on.');
+    if (is_installed) {
+        console.log('Plugin "' + plugin_id + '" already installed, \'sall good.');
         if (callback) callback();
         return;
     }
@@ -80,7 +83,7 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
     var dependencies = plugin_et.findall('dependency');
     if (dependencies && dependencies.length) {
         var end = n(dependencies.length, function() {
-            handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, callback);
+            handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback);
         });
         dependencies.forEach(function(dep) {
             var dep_plugin_id = dep.attrib.id;
@@ -92,18 +95,18 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
 
             if (fs.existsSync(path.join(plugins_dir, dep_plugin_id))) {
                 console.log('Dependent plugin ' + dep.attrib.id + ' already fetched, using that version.');
-                module.exports(platform, project_dir, dep_plugin_id, plugins_dir, dep_subdir, filtered_variables, www_dir, end);
+                module.exports(platform, project_dir, dep_plugin_id, plugins_dir, dep_subdir, filtered_variables, www_dir, false, end);
             } else {
                 console.log('Dependent plugin ' + dep.attrib.id + ' not fetched, retrieving then installing.');
-                module.exports(platform, project_dir, dep_url, plugins_dir, dep_subdir, filtered_variables, www_dir, end);
+                module.exports(platform, project_dir, dep_url, plugins_dir, dep_subdir, filtered_variables, www_dir, false, end);
             }
         });
     } else {
-        handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, callback);
+        handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback);
     }
 }
 
-function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, callback) {
+function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback) {
     var handler = platform_modules[platform];
     www_dir = www_dir || handler.www_dir(project_dir);
 
@@ -153,16 +156,12 @@ function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir,
                 console.log(info[0].text);
             }
 
-            finalizeInstall(project_dir, plugins_dir, platform, plugin_basename, filtered_variables, callback);
+            // queue up the plugin so prepare knows what to do.
+            config_changes.add_installed_plugin_to_prepare_queue(plugins_dir, plugin_basename, platform, filtered_variables, is_top_level);
+            // call prepare after a successful install
+            require('./../plugman').prepare(project_dir, platform, plugins_dir);
+
+            if (callback) callback();
         }
     });
 }
-
-function finalizeInstall(project_dir, plugins_dir, platform, plugin_name, variables, callback) {
-    // queue up the plugin so prepare knows what to do.
-    config_changes.add_installed_plugin_to_prepare_queue(plugins_dir, plugin_name, platform, variables);
-    // call prepare after a successful install
-    require('./../plugman').prepare(project_dir, platform, plugins_dir);
-
-    if (callback) callback();
-}

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/src/platforms/ios.js
----------------------------------------------------------------------
diff --git a/src/platforms/ios.js b/src/platforms/ios.js
index 68376b3..adc0325 100644
--- a/src/platforms/ios.js
+++ b/src/platforms/ios.js
@@ -72,7 +72,6 @@ module.exports = {
         },
         uninstall:function(header_el, project_dir, project) {
             var src = header_el.attrib['src'];
-            var srcFile = path.resolve(plugin_dir, src);
             var targetDir = path.resolve(project.plugins_dir, getRelativeDir(header_el));
             var destFile = path.resolve(targetDir, path.basename(src));
             project.xcode.removeHeaderFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/src/uninstall.js
----------------------------------------------------------------------
diff --git a/src/uninstall.js b/src/uninstall.js
index 35838ca..ee0cd62 100644
--- a/src/uninstall.js
+++ b/src/uninstall.js
@@ -2,9 +2,12 @@ var path = require('path'),
     fs   = require('fs'),
     et   = require('elementtree'),
     config_changes = require('./util/config-changes'),
+    action_stack = require('./util/action-stack'),
+    dependencies = require('./util/dependencies'),
+    underscore = require('underscore'),
     platform_modules = require('./platforms');
 
-module.exports = function uninstallPlugin(platform, project_dir, name, plugins_dir, cli_variables, www_dir, callback) {
+module.exports = function uninstallPlugin(platform, project_dir, id, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
     if (!platform_modules[platform]) {
         var err = new Error(platform + " not supported.");
         if (callback) callback(err);
@@ -12,8 +15,7 @@ module.exports = function uninstallPlugin(platform, project_dir, name, plugins_d
         return;
     }
 
-    // Check that the plugin has already been fetched.
-    var plugin_dir = path.join(plugins_dir, name);
+    var plugin_dir = path.join(plugins_dir, id);
 
     if (!fs.existsSync(plugin_dir)) {
         var err = new Error('Plugin "' + name + '" not found. Already uninstalled?');
@@ -22,101 +24,97 @@ module.exports = function uninstallPlugin(platform, project_dir, name, plugins_d
         return;
     }
 
-    runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, callback);
+    runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
 };
 
-function runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, callback) {
+function runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
     var xml_path     = path.join(plugin_dir, 'plugin.xml')
       , xml_text     = fs.readFileSync(xml_path, 'utf-8')
       , plugin_et    = new et.ElementTree(et.XML(xml_text))
     var name         = plugin_et.findall('name').text;
     var plugin_id    = plugin_et._root.attrib['id'];
 
-    var platformTag = plugin_et.find('./platform[@name="'+platform+'"]');
-    if (!platformTag) {
-        // Either this plugin doesn't support this platform, or it's a JS-only plugin.
-        // Either way, return now.
-        // should call prepare probably!
-        require('./../plugman').prepare(project_dir, platform, plugins_dir);
-        if (callback) callback();
-        return;
+    var dependency_info = dependencies.generate_dependency_info(plugins_dir, platform);
+    var graph = dependency_info.graph;
+    var dependents = graph.getChain(plugin_id);
+
+    var tlps = dependency_info.top_level_plugins;
+    var diff_arr = [];
+    tlps.forEach(function(tlp) {
+        if (tlp != plugin_id) {
+            var ds = graph.getChain(tlp);
+            if (is_top_level && ds.indexOf(plugin_id) > -1) {
+                // Another top-level plugin depends on this one
+                // Cannot uninstall then.
+                console.log('Another top-level plugin (' + tlp + ') relies on plugin ' + plugin_id + ', therefore, aborting uninstallation.');
+                if (callback) callback();
+                return;
+            }
+            diff_arr.push(ds);
+        }
+    });
+
+    if (dependents.length) {
+        // this plugin has dependencies
+        diff_arr.unshift(dependents);
+        // do a set difference to determine which dependencies are not required by other existing plugins
+        var danglers = underscore.difference.apply(null, diff_arr);
+        danglers && danglers.forEach(function(dangle) {
+            // TODO: fire off an uninstall for each danglin' dep
+        });
+    } else {
+        // this plugin is bare, uninstall it!
+        handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
     }
+}
 
+function handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback) {
     var platform_modules = require('./platforms');
-    // parse plugin.xml into transactions
     var handler = platform_modules[platform];
-    var txs = [];
-    var sourceFiles = platformTag.findall('./source-file'),
-        headerFiles = platformTag.findall('./header-file'),
-        resourceFiles = platformTag.findall('./resource-file'),
-        assets = platformTag.findall('./asset'),
-        frameworks = platformTag.findall('./framework');
-    assets = assets.concat(plugin_et.findall('./asset'));
+    var platformTag = plugin_et.find('./platform[@name="'+platform+'"]');
     www_dir = www_dir || handler.www_dir(project_dir);
 
-    // asset uninstallation
-    var uninstalledAssets = [];
-    var common = require('./platforms/common');
-    try {
-        for(var i = 0, j = assets.length ; i < j ; i++) {
-            common.removeFile(www_dir, assets[i].attrib['target']);
-            uninstalledAssets.push(assets[i]);
-        }
-        common.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
-    } catch(err) {
-        var issue = 'asset uninstallation failed\n'+err.stack+'\n';
-        try {
-            // adding assets back
-            for(var i = 0, j = uninstalledAssets.length ; i < j ; i++) {
-               var src = uninstalledAssets[i].attrib['src'],
-                   target = uninstalledAssets[i].attrib['target'];
-               common.copyFile(plugin_dir, src, www_dir, target);
-            }
-            issue += 'but successfully reverted\n';
-        } catch(err2) {
-            issue += 'and reversion failed :(\n' + err2.stack;
-        }
-        var error = new Error(issue);
-        if (callback) callback(error);
-        else throw error;
+    var assets = plugin_et.findall('./asset');
+    if (platformTag) {
+        var sourceFiles = platformTag.findall('./source-file'),
+            headerFiles = platformTag.findall('./header-file'),
+            resourceFiles = platformTag.findall('./resource-file'),
+            frameworks = platformTag.findall('./framework');
+        assets = assets.concat(platformTag.findall('./asset'));
+
+        // queue up native stuff
+        sourceFiles && sourceFiles.forEach(function(source) {
+            action_stack.push(action_stack.createAction(handler["source-file"].uninstall, [source, project_dir], handler["source-file"].install, [source, plugin_dir, project_dir]));
+        });
+
+        headerFiles && headerFiles.forEach(function(header) {
+            action_stack.push(action_stack.createAction(handler["header-file"].uninstall, [header, project_dir], handler["header-file"].install, [header, plugin_dir, project_dir]));
+        });
+
+        resourceFiles && resourceFiles.forEach(function(resource) {
+            action_stack.push(action_stack.createAction(handler["resource-file"].uninstall, [resource, project_dir], handler["resource-file"].install, [resource, plugin_dir, project_dir]));
+        });
+
+        frameworks && frameworks.forEach(function(framework) {
+            action_stack.push(action_stack.createAction(handler["framework"].uninstall, [framework, project_dir], handler["framework"].install, [framework, plugin_dir, project_dir]));
+        });
     }
-    
-    txs = txs.concat(sourceFiles, headerFiles, resourceFiles, frameworks);
-    
-    // pass platform-specific transactions into uninstall
-    handler.uninstall(txs, plugin_id, project_dir, plugin_dir, function(err) {
+
+    // queue up asset installation
+    var common = require('./platforms/common');
+    assets && assets.forEach(function(asset) {
+        action_stack.push(action_stack.createAction(common.asset.uninstall, [asset, www_dir, plugin_id], common.asset.install, [asset, plugin_dir, www_dir]));
+    });
+
+    // run through the action stack
+    action_stack.process(platform, project_dir, function(err) {
         if (err) {
-            // FAIL
-            var issue = '';
-            try {
-                for(var i = 0, j = uninstalledAssets.length ; i < j ; i++) {
-                    var src = uninstalledAssets[i].attrib['src'],
-                           target = uninstalledAssets[i].attrib['target'];
-                    common.copyFile(plugin_dir, src, www_dir, target);
-                }
-            } catch(err2) {
-                issue += 'Could not revert assets' + err2.stack + '\n';
-            }
-            if (err. transactions) {
-                handler.install(err.transactions.executed, plugin_id, project_dir, plugin_dir, cli_variables, function(superr) {
-                    if (superr) {
-                        // Even reversion failed. super fail.
-                        issue += 'Uninstall failed, then reversion of uninstallation failed. Sorry :(. Uninstalation issue: ' + err.stack + ', reversion issue: ' + superr.stack;
-                    } else {
-                        issue += 'Uninstall failed, plugin reversion successful so you should be good to go. Uninstallation issue: ' + err.stack;
-                    }
-                    var error = new Error(issue);
-                    if (callback) callback(error);
-                    else throw error;
-                });
-            } else {
-                if (callback) callback(err);
-                else throw err;
-            }
+            console.error(err.message, err.stack);
+            console.error('Plugin uninstallation failed :(');
         } else {
             // WIN!
             // queue up the plugin so prepare can remove the config changes
-            config_changes.add_uninstalled_plugin_to_prepare_queue(plugins_dir, path.basename(plugin_dir), platform);
+            config_changes.add_uninstalled_plugin_to_prepare_queue(plugins_dir, path.basename(plugin_dir), platform, is_top_level);
             // call prepare after a successful uninstall
             require('./../plugman').prepare(project_dir, platform, plugins_dir);
             if (callback) callback();

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/src/util/action-stack.js
----------------------------------------------------------------------
diff --git a/src/util/action-stack.js b/src/util/action-stack.js
index d9ef10e..a76f33c 100644
--- a/src/util/action-stack.js
+++ b/src/util/action-stack.js
@@ -34,7 +34,7 @@ module.exports = {
                 handler.apply(null, action_params);
             } catch(e) {
                 var incomplete = stack.unshift(action);
-                var issue = 'Install failed!\n';
+                var issue = 'Uh oh!\n';
                 // revert completed tasks
                 while(completed.length) {
                     var undo = completed.shift();

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/src/util/config-changes.js
----------------------------------------------------------------------
diff --git a/src/util/config-changes.js b/src/util/config-changes.js
index 4ff30e9..83eefb8 100644
--- a/src/util/config-changes.js
+++ b/src/util/config-changes.js
@@ -32,19 +32,19 @@ function checkPlatform(platform) {
 }
 
 module.exports = {
-    add_installed_plugin_to_prepare_queue:function(plugins_dir, plugin, platform, vars) {
+    add_installed_plugin_to_prepare_queue:function(plugins_dir, plugin, platform, vars, is_top_level) {
         checkPlatform(platform);
 
         var config = module.exports.get_platform_json(plugins_dir, platform);
-        config.prepare_queue.installed.push({'plugin':plugin, 'vars':vars});
+        config.prepare_queue.installed.push({'plugin':plugin, 'vars':vars, 'topLevel':is_top_level});
         module.exports.save_platform_json(config, plugins_dir, platform);
     },
-    add_uninstalled_plugin_to_prepare_queue:function(plugins_dir, plugin, platform) {
+    add_uninstalled_plugin_to_prepare_queue:function(plugins_dir, plugin, platform, is_top_level) {
         checkPlatform(platform);
 
         var plugin_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(plugins_dir, plugin, 'plugin.xml'), 'utf-8')));
         var config = module.exports.get_platform_json(plugins_dir, platform);
-        config.prepare_queue.uninstalled.push({'plugin':plugin, 'id':plugin_xml._root.attrib['id']});
+        config.prepare_queue.uninstalled.push({'plugin':plugin, 'id':plugin_xml._root.attrib['id'], 'topLevel':is_top_level});
         module.exports.save_platform_json(config, plugins_dir, platform);
     },
     get_platform_json:function(plugins_dir, platform) {
@@ -57,7 +57,8 @@ module.exports = {
             var config = {
                 prepare_queue:{installed:[], uninstalled:[]},
                 config_munge:{},
-                installed_plugins:{}
+                installed_plugins:{},
+                dependent_plugins:{}
             };
             fs.writeFileSync(filepath, JSON.stringify(config), 'utf-8');
             return config;
@@ -139,7 +140,7 @@ module.exports = {
         platform_config.prepare_queue.uninstalled.forEach(function(u) {
             var plugin_dir = path.join(plugins_dir, u.plugin);
             var plugin_id = u.id;
-            var plugin_vars = platform_config.installed_plugins[plugin_id];
+            var plugin_vars = (u.topLevel ? platform_config.installed_plugins[plugin_id] : platform_config.dependent_plugins[plugin_id]);
 
             // get config munge, aka how did this plugin change various config files
             var config_munge = module.exports.generate_plugin_config_munge(plugin_dir, platform, project_dir, plugin_vars);
@@ -210,7 +211,11 @@ module.exports = {
             platform_config.config_munge = global_munge;
 
             // Remove from installed_plugins
-            delete platform_config.installed_plugins[plugin_id]
+            if (u.topLevel) {
+                delete platform_config.installed_plugins[plugin_id]
+            } else {
+                delete platform_config.dependent_plugins[plugin_id]
+            }
         });
 
         // Empty out uninstalled queue.
@@ -292,8 +297,12 @@ module.exports = {
             });
             platform_config.config_munge = global_munge;
 
-            // Move to installed_plugins
-            platform_config.installed_plugins[plugin_id] = plugin_vars || {};
+            // Move to installed_plugins if it is a top-level plugin
+            if (u.topLevel) {
+                platform_config.installed_plugins[plugin_id] = plugin_vars || {};
+            } else {
+                platform_config.dependent_plugins[plugin_id] = plugin_vars || {};
+            }
         });
 
         // Empty out installed queue.

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/be96c196/src/util/dependencies.js
----------------------------------------------------------------------
diff --git a/src/util/dependencies.js b/src/util/dependencies.js
new file mode 100644
index 0000000..ce77dbf
--- /dev/null
+++ b/src/util/dependencies.js
@@ -0,0 +1,34 @@
+var dep_graph = require('dep-graph'),
+    path = require('path'),
+    config_changes = require('./config-changes'),
+    xml_helpers = require('./xml-helpers'),
+    underscore= require('underscore');
+
+module.exports = {
+    generate_dependency_info:function(plugins_dir, platform) {
+        var json = config_changes.get_platform_json(plugins_dir, platform);
+        var tlps = [];
+        var graph = new dep_graph();
+        Object.keys(json.installed_plugins).forEach(function(tlp) {
+            tlps.push(tlp);
+            var xml = xml_helpers.parseElementtreeSync(path.join(plugins_dir, tlp, 'plugin.xml'));
+            var deps = xml.findall('dependency');
+            deps && deps.forEach(function(dep) {
+                var id = dep.attrib.id;
+                graph.add(tlp, id);
+            });
+        });
+        Object.keys(json.dependent_plugins).forEach(function(plug) {
+            var xml = xml_helpers.parseElementtreeSync(path.join(plugins_dir, plug, 'plugin.xml'));
+            var deps = xml.findall('dependency');
+            deps && deps.forEach(function(dep) {
+                var id = dep.attrib.id;
+                graph.add(plug, id);
+            });
+        });
+        return {
+            graph:graph,
+            top_level_plugins:tlps
+        };
+    }
+};


[05/20] git commit: added specs for fetch and clonePluginGitRepo w.r.t. subdirectory support

Posted by fi...@apache.org.
added specs for fetch and clonePluginGitRepo w.r.t. subdirectory support


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/694ce2a3
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/694ce2a3
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/694ce2a3

Branch: refs/heads/master
Commit: 694ce2a35aa83e3ba46a0a754807c825c8a0577c
Parents: 8d0e95d
Author: Fil Maj <ma...@gmail.com>
Authored: Fri May 10 14:22:26 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 spec/fetch.spec.js        |    7 ++++++
 spec/util/plugins.spec.js |   42 ++++++++++++++++++++++++++++-----------
 src/util/plugins.js       |    5 ++-
 3 files changed, 40 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/694ce2a3/spec/fetch.spec.js
----------------------------------------------------------------------
diff --git a/spec/fetch.spec.js b/spec/fetch.spec.js
index b36ceab..8f7c678 100644
--- a/spec/fetch.spec.js
+++ b/spec/fetch.spec.js
@@ -37,6 +37,13 @@ describe('fetch', function() {
             fetch("https://github.com/bobeast/GAPlugin.git", temp, false);
             expect(s).toHaveBeenCalled();
         });
+        it('should call clonePluginGitRepo with subdir if applicable', function() {
+            var s = spyOn(plugins, 'clonePluginGitRepo');
+            var url = "https://github.com/bobeast/GAPlugin.git";
+            var dir = 'fakeSubDir';
+            fetch(url, temp, false, dir);
+            expect(s).toHaveBeenCalledWith(url, temp, dir, undefined);
+        });
         it('should throw if used with url and `link` param', function() {
             expect(function() {
                 fetch("https://github.com/bobeast/GAPlugin.git", temp, true);

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/694ce2a3/spec/util/plugins.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/plugins.spec.js b/spec/util/plugins.spec.js
index f0cb3e0..d2af311 100644
--- a/spec/util/plugins.spec.js
+++ b/spec/util/plugins.spec.js
@@ -23,22 +23,40 @@ var http   = require('http'),
     fs     = require('fs'),
     temp   = path.join(osenv.tmpdir(), 'plugman'),
     shell  = require('shelljs'),
-    plugins = require('../../src/util/plugins');
+    plugins = require('../../src/util/plugins'),
+    xml_helpers = require('../../src/util/xml-helpers');
 
-describe('plugins', function(){
-    describe('server', function(){
-        it('should be able to receive the correct git clone arguments', function(){
-            var mySpy = spyOn(plugins, 'clonePluginGitRepo');
+describe('plugins utility module', function(){
+    describe('clonePluginGitRepo', function(){
+        it('should shell out to git clone with correct arguments', function(){
+            var mySpy = spyOn(shell, 'exec');
             var plugin_git_url = 'https://github.com/imhotep/ChildBrowser'
-            var myFunction = function(){};
             
-            plugins.clonePluginGitRepo(plugin_git_url, temp, '.', myFunction);
+            plugins.clonePluginGitRepo(plugin_git_url, temp, '.');
             
-             expect(mySpy).toHaveBeenCalled();
-             expect(mySpy).toHaveBeenCalledWith(plugin_git_url, temp, '.', myFunction);
+            expect(mySpy).toHaveBeenCalled();
+            var git_clone_regex = new RegExp('^git clone "' + plugin_git_url + '" ".*"$', 'gi');
+            expect(mySpy.mostRecentCall.args[0]).toMatch(git_clone_regex);
+        });
+        it('should take into account subdirectory argument when copying over final repository into plugins+plugin_id directory', function() {
+            var exec_spy = spyOn(shell, 'exec');
+            var cp_spy = spyOn(shell, 'cp');
+            var fake_id = 'VillageDrunkard';
+            var xml_spy = spyOn(xml_helpers, 'parseElementtreeSync').andReturn({
+                getroot:function() {
+                    return {
+                        attrib:{id:fake_id}
+                    };
+                }
+            });
+            var plugin_git_url = 'https://github.com/imhotep/ChildBrowser'
+            
+            var fake_subdir = 'TheBrainRecoilsInHorror';
+            plugins.clonePluginGitRepo(plugin_git_url, temp, fake_subdir);
+            exec_spy.mostRecentCall.args[2](0); // fake out shell.exec finishing appropriately (so we dont ACTUALLY shell out to git-clone, assume it worked fine)
+            var expected_subdir_cp_path = new RegExp(fake_subdir + '[\\\\\\/]\\*$', 'gi');
+            expect(cp_spy.mostRecentCall.args[1]).toMatch(expected_subdir_cp_path);
+            expect(cp_spy.mostRecentCall.args[2]).toEqual(path.join(temp, fake_id));
         });
     });
 });
-
-
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/694ce2a3/src/util/plugins.js
----------------------------------------------------------------------
diff --git a/src/util/plugins.js b/src/util/plugins.js
index fddecfa..ff9cace 100644
--- a/src/util/plugins.js
+++ b/src/util/plugins.js
@@ -42,12 +42,13 @@ module.exports = {
         var cmd = util.format('git clone "%s" "%s"', plugin_git_url, tmp_dir);
         shell.exec(cmd, {silent: true, async:true}, function(code, output) {
             if (code > 0) {
-                var err = new Error('failed to get the plugin via git from URL '+ plugin_git_url);
+                var err = new Error('failed to get the plugin via git from URL '+ plugin_git_url + ', output: ' + output);
                 if (callback) callback(err)
                 else throw err;
             } else {
                 // Read the plugin.xml file and extract the plugin's ID.
                 tmp_dir = path.join(tmp_dir, subdir);
+                // TODO: what if plugin.xml does not exist?
                 var xml_file = path.join(tmp_dir, 'plugin.xml');
                 var xml = xml_helpers.parseElementtreeSync(xml_file);
                 var plugin_id = xml.getroot().attrib.id;
@@ -58,6 +59,6 @@ module.exports = {
                 if (callback) callback(null, plugin_dir);
             }
         });
-    },
+    }
 };
 


[02/20] git commit: Fix source and destination paths for installing plugins from local disk

Posted by fi...@apache.org.
Fix source and destination paths for installing plugins from local disk

They were installing into eg. my.plugin.id/plugman_dir_basename/*
instead of just my.plugin.id/*.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/58cbc81d
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/58cbc81d
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/58cbc81d

Branch: refs/heads/master
Commit: 58cbc81dc244d3cdb0bd1f48e03389281e14d629
Parents: 5a054f3
Author: Braden Shepherdson <br...@gmail.com>
Authored: Tue May 14 13:54:28 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 src/fetch.js |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/58cbc81d/src/fetch.js
----------------------------------------------------------------------
diff --git a/src/fetch.js b/src/fetch.js
index 8d6c7b8..73bf4e2 100644
--- a/src/fetch.js
+++ b/src/fetch.js
@@ -32,7 +32,8 @@ module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, cal
         if (link) {
             fs.symlinkSync(plugin_dir, dest, 'dir');
         } else {
-            shell.cp('-R', plugin_dir, dest);
+            shell.mkdir('-p', dest);
+            shell.cp('-R', path.join(plugin_dir, '*') , dest);
         }
 
         if (callback) callback(null, dest);


[18/20] git commit: tweaks for prepare specs

Posted by fi...@apache.org.
tweaks for prepare specs


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/f025a2a9
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/f025a2a9
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/f025a2a9

Branch: refs/heads/master
Commit: f025a2a98a4bec04d825d0ac1bd55e4fce61b37a
Parents: bf56f3e
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 15:03:37 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:43 2013 -0700

----------------------------------------------------------------------
 spec/prepare.spec.js       |   11 ++++-------
 src/prepare.js             |   30 +-----------------------------
 src/util/config-changes.js |    5 ++---
 3 files changed, 7 insertions(+), 39 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/f025a2a9/spec/prepare.spec.js
----------------------------------------------------------------------
diff --git a/spec/prepare.spec.js b/spec/prepare.spec.js
index b3bce26..7fa49c0 100644
--- a/spec/prepare.spec.js
+++ b/spec/prepare.spec.js
@@ -1,4 +1,5 @@
-var prepare = require('../src/prepare'),
+var platforms = require('../src/platforms'),
+    prepare = require('../src/prepare'),
     fs      = require('fs'),
     os      = require('osenv'),
     path    = require('path'),
@@ -15,8 +16,8 @@ describe('prepare', function() {
     beforeEach(function() {
         shell.mkdir('-p', temp);
         shell.mkdir('-p', plugins_dir);
-        shell.cp('-rf', android_one_project, temp);
         shell.cp('-rf', childbrowser, plugins_dir);
+        shell.cp('-rf', android_one_project, temp);
     });
     afterEach(function() {
         shell.rm('-rf', temp);
@@ -28,12 +29,8 @@ describe('prepare', function() {
         prepare(temp, 'android', plugins_dir);
         expect(fs.existsSync(path.join(www, 'cordova_plugins.json'))).toBe(true);
     });
-//    it('should copy over assets defined in <asset> elements', function() {
-//        prepare(temp, 'android', plugins_dir);
-//        expect(fs.existsSync(path.join(www, 'childbrowser_file.html'))).toBe(true);
-//        expect(fs.statSync(path.join(www, 'childbrowser')).isDirectory()).toBe(true);
-//    });
     it('should create a plugins directory in an application\'s www directory', function() {
+        shell.cp('-rf', dummyplugin, plugins_dir);
         prepare(temp, 'android', plugins_dir);
         expect(fs.existsSync(path.join(www, 'plugins'))).toBe(true);
     });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/f025a2a9/src/prepare.js
----------------------------------------------------------------------
diff --git a/src/prepare.js b/src/prepare.js
index 49412b1..7f3df91 100644
--- a/src/prepare.js
+++ b/src/prepare.js
@@ -58,35 +58,7 @@ module.exports = function handlePrepare(project_dir, platform, plugins_dir) {
     
             var plugin_id = xml.getroot().attrib.id;
     
-            // Copy all the <asset>s into the platform's www/
-           // var assets = xml.findall('./asset');
-           // assets && assets.forEach(function(asset) {
-           //     var target = asset.attrib.target;
-           //    
-           //     var lastSlash = target.lastIndexOf('/');
-           //     var dirname  = lastSlash < 0 ? ''     : target.substring(0, lastSlash);
-           //     var basename = lastSlash < 0 ? target : target.substring(lastSlash + 1);
-    
-           //     var targetDir = path.join(wwwDir, dirname);
-           //      
-           //     shell.mkdir('-p', targetDir);
-    
-           //     var srcFile = path.join(pluginDir, asset.attrib.src);
-           //     var targetFile = path.join(targetDir, basename);
-    
-           //     var cpOptions = '-f';
-           //     
-           //     if(fs.statSync(srcFile).isDirectory()){
-           //         shell.mkdir('-p',targetFile);
-           //         srcFile = srcFile+'/*';
-           //         cpOptions = '-Rf';
-           //     }
-    
-           //     shell.cp(cpOptions, [srcFile], targetFile);
-           //     
-           // });
-    
-            // And then add the plugins dir to the platform's www.
+            // add the plugins dir to the platform's www.
             var platformPluginsDir = path.join(wwwDir, 'plugins');
             // XXX this should not be here if there are no js-module. It leaves an empty plugins/ directory
             shell.mkdir('-p', platformPluginsDir);

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/f025a2a9/src/util/config-changes.js
----------------------------------------------------------------------
diff --git a/src/util/config-changes.js b/src/util/config-changes.js
index 83eefb8..5276184 100644
--- a/src/util/config-changes.js
+++ b/src/util/config-changes.js
@@ -23,12 +23,11 @@ var fs   = require('fs'),
     plist = require('plist'),
     bplist = require('bplist-parser'),
     et   = require('elementtree'),
-    platforms = require('./../platforms'),
     xml_helpers = require('./../util/xml-helpers'),
     plist_helpers = require('./../util/plist-helpers');
 
 function checkPlatform(platform) {
-    if (!(platform in platforms)) throw new Error('platform "' + platform + '" not recognized.');
+    if (!(platform in require('./../platforms'))) throw new Error('platform "' + platform + '" not recognized.');
 }
 
 module.exports = {
@@ -74,7 +73,7 @@ module.exports = {
         checkPlatform(platform);
 
         vars = vars || {};
-        var platform_handler = platforms[platform];
+        var platform_handler = require('./../platforms')[platform];
         // Add PACKAGE_NAME variable into vars
         if (!vars['PACKAGE_NAME']) {
             vars['PACKAGE_NAME'] = platform_handler.package_name(project_dir);


[04/20] git commit: Adding subdirectory support to fetch.

Posted by fi...@apache.org.
Adding subdirectory support to fetch.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/1a6285be
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/1a6285be
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/1a6285be

Branch: refs/heads/master
Commit: 1a6285be074fdab0ebb58a035e5cf3ead6b6c09c
Parents: 3aaa5a8
Author: Braden Shepherdson <br...@gmail.com>
Authored: Thu May 9 12:54:57 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 spec/util/plugins.spec.js |    4 ++--
 src/fetch.js              |   11 +++++++----
 src/install.js            |    2 +-
 src/util/plugins.js       |    5 +++--
 4 files changed, 13 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/1a6285be/spec/util/plugins.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/plugins.spec.js b/spec/util/plugins.spec.js
index 55c3f0e..f0cb3e0 100644
--- a/spec/util/plugins.spec.js
+++ b/spec/util/plugins.spec.js
@@ -32,10 +32,10 @@ describe('plugins', function(){
             var plugin_git_url = 'https://github.com/imhotep/ChildBrowser'
             var myFunction = function(){};
             
-            plugins.clonePluginGitRepo(plugin_git_url, temp, myFunction);
+            plugins.clonePluginGitRepo(plugin_git_url, temp, '.', myFunction);
             
              expect(mySpy).toHaveBeenCalled();
-             expect(mySpy).toHaveBeenCalledWith(plugin_git_url, temp, myFunction);
+             expect(mySpy).toHaveBeenCalledWith(plugin_git_url, temp, '.', myFunction);
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/1a6285be/src/fetch.js
----------------------------------------------------------------------
diff --git a/src/fetch.js b/src/fetch.js
index 822f28d..8d6c7b8 100644
--- a/src/fetch.js
+++ b/src/fetch.js
@@ -4,10 +4,12 @@ var shell   = require('shelljs'),
     xml_helpers = require('./util/xml-helpers'),
     path    = require('path');
 
-module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, callback) {
+module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, subdir, callback) {
     // Ensure the containing directory exists.
     shell.mkdir('-p', plugins_dir);
 
+    subdir = subdir || '.';
+
     // clone from git repository
     if(plugin_dir.indexOf('https://') == 0 || plugin_dir.indexOf('git://') == 0) {
         if (link) {
@@ -15,11 +17,12 @@ module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, callback) {
             if (callback) callback(err);
             else throw err;
         } else {
-            plugins.clonePluginGitRepo(plugin_dir, plugins_dir, callback);
+            plugins.clonePluginGitRepo(plugin_dir, plugins_dir, subdir, callback);
         }
     } else {
         // Copy from the local filesystem.
         // First, read the plugin.xml and grab the ID.
+        plugin_dir = path.join(plugin_dir, subdir);
         var xml = xml_helpers.parseElementtreeSync(path.join(plugin_dir, 'plugin.xml'));
         var plugin_id = xml.getroot().attrib.id;
 
@@ -27,9 +30,9 @@ module.exports = function fetchPlugin(plugin_dir, plugins_dir, link, callback) {
 
         shell.rm('-rf', dest);
         if (link) {
-            fs.symlinkSync(path.resolve(plugin_dir), dest, 'dir');
+            fs.symlinkSync(plugin_dir, dest, 'dir');
         } else {
-            shell.cp('-R', path.join(plugin_dir, '*'), dest);
+            shell.cp('-R', plugin_dir, dest);
         }
 
         if (callback) callback(null, dest);

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/1a6285be/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 617b065..257d8de 100644
--- a/src/install.js
+++ b/src/install.js
@@ -20,7 +20,7 @@ module.exports = function installPlugin(platform, project_dir, name, plugins_dir
     // Check that the plugin has already been fetched.
     if (!fs.existsSync(plugin_dir)) {
         // if plugin doesnt exist, use fetch to get it.
-        require('../plugman').fetch(name, plugins_dir, false, function(err, plugin_dir) {
+        require('../plugman').fetch(name, plugins_dir, false, '.', function(err, plugin_dir) {
             if (err) {
                 callback(err);
             } else {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/1a6285be/src/util/plugins.js
----------------------------------------------------------------------
diff --git a/src/util/plugins.js b/src/util/plugins.js
index 0a627cb..fddecfa 100644
--- a/src/util/plugins.js
+++ b/src/util/plugins.js
@@ -25,12 +25,12 @@ var http = require('http'),
     util = require('util'),
     shell = require('shelljs'),
     xml_helpers = require('./xml-helpers'),
-    tmp_dir = path.join(path.sep, 'tmp', 'plugman-tmp');
+    tmp_dir = path.join(os.tmpdir(), 'plugman-tmp');
 
 module.exports = {
     searchAndReplace:require('./search-and-replace'),
     // Fetches plugin information from remote server
-    clonePluginGitRepo:function(plugin_git_url, plugins_dir, callback) {
+    clonePluginGitRepo:function(plugin_git_url, plugins_dir, subdir, callback) {
         if(!shell.which('git')) {
             var err = new Error('git command line is not installed');
             if (callback) callback(err);
@@ -47,6 +47,7 @@ module.exports = {
                 else throw err;
             } else {
                 // Read the plugin.xml file and extract the plugin's ID.
+                tmp_dir = path.join(tmp_dir, subdir);
                 var xml_file = path.join(tmp_dir, 'plugin.xml');
                 var xml = xml_helpers.parseElementtreeSync(xml_file);
                 var plugin_id = xml.getroot().attrib.id;


[03/20] git commit: Add spec for tag to README.md.

Posted by fi...@apache.org.
Add spec for <dependency> tag to README.md.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/5a054f37
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/5a054f37
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/5a054f37

Branch: refs/heads/master
Commit: 5a054f37580f7f2013dde06b72f816abab164983
Parents: 694ce2a
Author: Braden Shepherdson <br...@gmail.com>
Authored: Mon May 13 16:45:43 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 README.md |   13 +++++++++++++
 1 files changed, 13 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/5a054f37/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index fa68ae9..833fb23 100644
--- a/README.md
+++ b/README.md
@@ -232,6 +232,19 @@ If `src` does not resolve to a file that can be found, plugman will stop/reverse
 &lt;js-module&gt; elements can also be nested under &lt;platform&gt;, to declare platform-specific JavaScript module bindings.
 
 
+### &lt;dependency&gt;
+
+Dependency tags let you specify plugins on which this plugin depends. In the future there will be plugin repositories to fetch plugins from. In the short term, plugins are directly pointed to by URLs in `<dependency>` tags. These tags have the following format:
+
+    <dependency id="com.plugin.id" src="https://github.com/myuser/someplugin" commit="428931ada3891801" subdir="some/path/here" />
+
+* `id`: gives the ID of the plugin. This should be globally unique, and in reverse-domain style. Neither of these restrictions is currently enforced, but they may be in the future and plugins should still follow them.
+* `src`: A URL for the plugin. This should point to a git repository, since plugman will try to `git clone` it.
+* `commit`: This is any git ref. It can be a branch or tag name (eg. `master`, `0.3.1`), a commit hash (eg. `975ddb228af811dd8bb37ed1dfd092a3d05295f9`), anything understood by `git checkout`.
+* `subdir`: Specifies that the plugin we're interested in exists as a subdirectory of the git repository. This is helpful because it allows one to keep several related plugins in a sigle git repository, and specify the plugins in it individually.
+
+In the future, version constraints will be introduced, and a plugin repository will exist to support fetching by name instead of explicit URLs.
+
 ### &lt;platform&gt;
 
 Platform tags identify platforms that have associated native code and/or require configuration file modifications. Tools using


[11/20] git commit: slight workaround to avoid duplication operations during ios installs

Posted by fi...@apache.org.
slight workaround to avoid duplication operations during ios installs


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/adf3e1e8
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/adf3e1e8
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/adf3e1e8

Branch: refs/heads/master
Commit: adf3e1e8636e3b661532924ee5e4c2599e4281bc
Parents: 2e5f15f
Author: Fil Maj <ma...@gmail.com>
Authored: Tue May 14 14:14:42 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:42 2013 -0700

----------------------------------------------------------------------
 src/install.js           |    2 +-
 src/platforms/ios.js     |  121 +++++++++++++++--------------------------
 src/util/action-stack.js |   15 +++++-
 3 files changed, 60 insertions(+), 78 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/adf3e1e8/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 74c8e41..cd5652f 100644
--- a/src/install.js
+++ b/src/install.js
@@ -141,7 +141,7 @@ function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir,
     });
 
     // run through the action stack
-    action_stack.process(function(err) {
+    action_stack.process(platform, project_dir, function(err) {
         if (err) {
             console.error(err.message, err.stack);
             console.error('Plugin installation failed :(');

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/adf3e1e8/src/platforms/ios.js
----------------------------------------------------------------------
diff --git a/src/platforms/ios.js b/src/platforms/ios.js
index c0187c5..68376b3 100644
--- a/src/platforms/ios.js
+++ b/src/platforms/ios.js
@@ -33,8 +33,7 @@ module.exports = {
         return plist.parseFileSync(plist_file).CFBundleIdentifier;
     },
     "source-file":{
-        install:function(source_el, plugin_dir, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        install:function(source_el, plugin_dir, project_dir, project) {
             var src = source_el.attrib['src'];
             var srcFile = path.resolve(plugin_dir, src);
             var targetDir = path.resolve(project.plugins_dir, getRelativeDir(source_el));
@@ -45,11 +44,8 @@ module.exports = {
             project.xcode.addSourceFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));
             shell.mkdir('-p', targetDir);
             shell.cp(srcFile, destFile);
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         },
-        uninstall:function(source_el, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        uninstall:function(source_el, project_dir, project) {
             var src = source_el.attrib['src'];
             var targetDir = path.resolve(project.plugins_dir, getRelativeDir(source_el));
             var destFile = path.resolve(targetDir, path.basename(src));
@@ -60,13 +56,10 @@ module.exports = {
             if(fs.existsSync(targetDir) && fs.readdirSync(targetDir).length>0){
                 shell.rm('-rf', targetDir); 
             }
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         }
     },
     "header-file":{
-        install:function(header_el, plugin_dir, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        install:function(header_el, plugin_dir, project_dir, project) {
             var src = header_el.attrib['src'];
             var srcFile = path.resolve(plugin_dir, src);
             var targetDir = path.resolve(project.plugins_dir, getRelativeDir(header_el));
@@ -76,11 +69,8 @@ module.exports = {
             project.xcode.addHeaderFile(path.join('Plugins', path.relative(project.plugins_dir, destFile)));
             shell.mkdir('-p', targetDir);
             shell.cp(srcFile, destFile);
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         },
-        uninstall:function(header_el, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        uninstall:function(header_el, project_dir, project) {
             var src = header_el.attrib['src'];
             var srcFile = path.resolve(plugin_dir, src);
             var targetDir = path.resolve(project.plugins_dir, getRelativeDir(header_el));
@@ -90,98 +80,77 @@ module.exports = {
             if(fs.existsSync(targetDir) && fs.readdirSync(targetDir).length>0){
                 shell.rm('-rf', targetDir); 
             }
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         }
     },
     "resource-file":{
-        install:function(resource_el, plugin_dir, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        install:function(resource_el, plugin_dir, project_dir, project) {
             var src = resource_el.attrib['src'],
                 srcFile = path.resolve(plugin_dir, src),
                 destFile = path.resolve(project.resources_dir, path.basename(src));
             if (!fs.existsSync(srcFile)) throw new Error('cannot find "' + srcFile + '" ios <resource-file>');
             if (fs.existsSync(destFile)) throw new Error('target destination "' + destFile + '" already exists');
             project.xcode.addResourceFile(path.join('Resources', path.basename(src)));
-            shell.cp('-R', srcFile, resourcesDir);
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
+            shell.cp('-R', srcFile, project.resources_dir);
         },
-        uninstall:function(resource_el, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        uninstall:function(resource_el, project_dir, project) {
             var src = resource_el.attrib['src'],
                 destFile = path.resolve(project.resources_dir, path.basename(src));
             project.xcode.removeResourceFile(path.join('Resources', path.basename(src)));
             shell.rm('-rf', destFile);
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         }
     },
     "framework":{
-        install:function(framework_el, plugin_dir, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        install:function(framework_el, plugin_dir, project_dir, project) {
             var src = framework_el.attrib['src'],
                 weak = framework_el.attrib['weak'];
             var opt = { weak: (weak == undefined || weak == null || weak != 'true' ? false : true ) };
             project.xcode.addFramework(src, opt);
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         },
-        uninstall:function(framework_el, project_dir) {
-            var project = getIOSProjectFiles(project_dir);
+        uninstall:function(framework_el, project_dir, project) {
             var src = framework_el.attrib['src'];
             project.xcode.removeFramework(src);
-            // write out xcodeproj file
-            fs.writeFileSync(project.pbx, project.xcode.writeSync());
         }
-    }
-};
- 
-function getIOSProjectFiles(project_dir) {
-    // grab and parse pbxproj
-    // we don't want CordovaLib's xcode project
-    var project_files = glob.sync(path.join(project_dir, '*.xcodeproj', 'project.pbxproj'));
-    
-    if (project_files.length === 0) {
-        throw new Error("does not appear to be an xcode project (no xcode project file)");
-    }
-    var pbxPath = project_files[0];
-    var xcodeproj = xcode.project(pbxPath);
-    xcodeproj.parseSync();
+    },
+    parseIOSProjectFiles:function(project_dir) {
+        // grab and parse pbxproj
+        // we don't want CordovaLib's xcode project
+        var project_files = glob.sync(path.join(project_dir, '*.xcodeproj', 'project.pbxproj'));
+        
+        if (project_files.length === 0) {
+            throw new Error("does not appear to be an xcode project (no xcode project file)");
+        }
+        var pbxPath = project_files[0];
+        var xcodeproj = xcode.project(pbxPath);
+        xcodeproj.parseSync();
 
-    // grab and parse plist file or config.xml
-    var config_files = (glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist')).length == 0 ? 
-                        glob.sync(path.join(project_dir, '**', 'config.xml')) :
-                        glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist'))
-                       );
+        // grab and parse plist file or config.xml
+        var config_files = (glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist')).length == 0 ? 
+                            glob.sync(path.join(project_dir, '**', 'config.xml')) :
+                            glob.sync(path.join(project_dir, '**', '{PhoneGap,Cordova}.plist'))
+                           );
 
-    config_files = config_files.filter(function (val) {
-        return !(/^build\//.test(val));
-    });
+        config_files = config_files.filter(function (val) {
+            return !(/^build\//.test(val));
+        });
 
-    if (config_files.length === 0) {
-        throw new Error("could not find PhoneGap/Cordova plist file, or config.xml file.");
-    }
-
-    var config_file = config_files[0];
-    var config_filename = path.basename(config_file);
-    var xcode_dir = path.dirname(config_file);
-    var pluginsDir = path.resolve(xcode_dir, 'Plugins');
-    var resourcesDir = path.resolve(xcode_dir, 'Resources');
-    // get project plist for package name
-    var project_plists = glob.sync(xcode_dir + '/*-Info.plist');
-    var projectPListPath = project_plists[0];
+        if (config_files.length === 0) {
+            throw new Error("could not find PhoneGap/Cordova plist file, or config.xml file.");
+        }
 
-    // for certain config changes, we need to know if plugins-plist elements are present
-    var plistEle = txs.filter(function(t) { return t.tag.toLowerCase() == 'plugins-plist'; })[0];
+        var config_file = config_files[0];
+        var config_filename = path.basename(config_file);
+        var xcode_dir = path.dirname(config_file);
+        var pluginsDir = path.resolve(xcode_dir, 'Plugins');
+        var resourcesDir = path.resolve(xcode_dir, 'Resources');
 
-    return {
-        plugins_dir:pluginsDir,
-        resources_dir:resourcesDir,
-        xcode:xcodeproj,
-        pbx:pbxPath
-    };
-}
+        return {
+            plugins_dir:pluginsDir,
+            resources_dir:resourcesDir,
+            xcode:xcodeproj,
+            pbx:pbxPath
+        };
+    }
+};
 
 function getRelativeDir(file) {
     var targetDir = file.attrib['target-dir'];

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/adf3e1e8/src/util/action-stack.js
----------------------------------------------------------------------
diff --git a/src/util/action-stack.js b/src/util/action-stack.js
index 849bb54..d9ef10e 100644
--- a/src/util/action-stack.js
+++ b/src/util/action-stack.js
@@ -1,3 +1,6 @@
+var ios = require('../platforms/ios'),
+    fs = require('fs');
+
 var stack = [];
 var completed = [];
 
@@ -17,11 +20,16 @@ module.exports = {
     push:function(tx) {
         stack.push(tx);
     },
-    process:function(callback) {
+    process:function(platform, project_dir, callback) {
+        if (platform == 'ios') {
+            // parse xcode project file once
+            var project_files = ios.parseIOSProjectFiles(project_dir);
+        }
         while(stack.length) {
             var action = stack.shift();
             var handler = action.handler.run;
             var action_params = action.handler.params;
+            if (platform == 'ios') action_params.push(project_files);
             try {
                 handler.apply(null, action_params);
             } catch(e) {
@@ -32,6 +40,7 @@ module.exports = {
                     var undo = completed.shift();
                     var revert = undo.reverter.run;
                     var revert_params = undo.reverter.params;
+                    if (platform == 'ios') revert_params.push(project_files);
                     try {
                         revert.apply(null, revert_params);
                     } catch(err) {
@@ -45,6 +54,10 @@ module.exports = {
             }
             completed.push(action);
         }
+        if (platform == 'ios') {
+            // write out xcodeproj file
+            fs.writeFileSync(project_files.pbx, project_files.xcode.writeSync());
+        }
         if (callback) callback();
     }
 };


[09/20] git commit: updated/fixed install + uninstall specs. added specs to both for plugins with dependencies

Posted by fi...@apache.org.
updated/fixed install + uninstall specs. added specs to both for plugins with dependencies


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/bf56f3ed
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/bf56f3ed
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/bf56f3ed

Branch: refs/heads/master
Commit: bf56f3eddda3f88cb09bfd487a3c58d112c335e3
Parents: e05e51a
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 12:59:07 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:42 2013 -0700

----------------------------------------------------------------------
 main.js                                |    4 +-
 spec/install.spec.js                   |  127 ++++++++++++++-------------
 spec/plugins/dependencies/A/plugin.xml |   60 +++++++++++++
 spec/plugins/dependencies/B/plugin.xml |   60 +++++++++++++
 spec/plugins/dependencies/C/plugin.xml |   57 ++++++++++++
 spec/plugins/dependencies/D/plugin.xml |   57 ++++++++++++
 spec/plugins/dependencies/E/plugin.xml |   57 ++++++++++++
 spec/plugins/dependencies/README.md    |    5 +
 spec/uninstall.spec.js                 |   92 +++++++++-----------
 src/install.js                         |   46 ++++++----
 src/uninstall.js                       |   31 ++++---
 src/util/action-stack.js               |   24 +++--
 12 files changed, 462 insertions(+), 158 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/main.js
----------------------------------------------------------------------
diff --git a/main.js b/main.js
index 414cf18..8339061 100755
--- a/main.js
+++ b/main.js
@@ -63,7 +63,7 @@ else if (!cli_opts.platform || !cli_opts.project || !cli_opts.plugin) {
     printUsage();
 }
 else if (cli_opts.uninstall) {
-    plugman.uninstall(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, {}, cli_opts.www, true /* is top level? */);
+    plugman.uninstall(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, {}, cli_opts.www);
 }
 else {
     var cli_variables = {}
@@ -74,7 +74,7 @@ else {
             if (/^[\w-_]+$/.test(key)) cli_variables[key] = tokens.join('=');
         });
     }
-    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, '.', cli_variables, cli_opts.www, true /* is top level? */);
+    plugman.install(cli_opts.platform, cli_opts.project, cli_opts.plugin, plugins_dir, '.', cli_variables, cli_opts.www);
 }
 
 function printUsage() {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/common.spec.js
----------------------------------------------------------------------
diff --git a/spec/common.spec.js b/spec/common.spec.js
deleted file mode 100644
index e69de29..0000000

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/install.spec.js
----------------------------------------------------------------------
diff --git a/spec/install.spec.js b/spec/install.spec.js
index c471747..34bc9e2 100644
--- a/spec/install.spec.js
+++ b/spec/install.spec.js
@@ -1,6 +1,6 @@
 var install = require('../src/install'),
-    android = require('../src/platforms/android'),
     common = require('../src/platforms/common'),
+    actions = require('../src/util/action-stack'),
     //ios     = require('../src/platforms/ios'),
     //blackberry = require('../src/platforms/blackberry'),
     config_changes = require('../src/util/config-changes'),
@@ -11,7 +11,13 @@ var install = require('../src/install'),
     shell   = require('shelljs'),
     temp    = path.join(os.tmpdir(), 'plugman'),
     childbrowser = path.join(__dirname, 'plugins', 'ChildBrowser'),
+    dep_a = path.join(__dirname, 'plugins', 'dependencies', 'A'),
+    dep_b = path.join(__dirname, 'plugins', 'dependencies', 'B'),
+    dep_c = path.join(__dirname, 'plugins', 'dependencies', 'C'),
+    dep_d = path.join(__dirname, 'plugins', 'dependencies', 'D'),
+    dep_e = path.join(__dirname, 'plugins', 'dependencies', 'E'),
     dummyplugin = path.join(__dirname, 'plugins', 'DummyPlugin'),
+    dummy_id = 'com.phonegap.plugins.dummyplugin',
     variableplugin = path.join(__dirname, 'plugins', 'VariablePlugin'),
     faultyplugin = path.join(__dirname, 'plugins', 'FaultyPlugin'),
     android_one_project = path.join(__dirname, 'projects', 'android_one', '*');
@@ -24,26 +30,19 @@ describe('install', function() {
 
     beforeEach(function() {
         shell.mkdir('-p', temp);
-        shell.mkdir('-p', plugins_dir);
         shell.cp('-rf', android_one_project, temp);
     });
     afterEach(function() {
         shell.rm('-rf', temp);
     });
 
-
     describe('success', function() {
-        var android_installer;
-        beforeEach(function() {
-            shell.cp('-rf', dummyplugin, plugins_dir);
-            android_installer = spyOn(android, 'install');
-        });
         it('should properly install assets', function() {
             var s = spyOn(common, 'copyFile').andCallThrough();
-            install('android', temp, 'DummyPlugin', plugins_dir, {});
+            install('android', temp, dummyplugin, plugins_dir, '.', {});
             // making sure the right methods were called
             expect(s).toHaveBeenCalled();
-            expect(s.calls.length).toEqual(2);
+            expect(s.calls.length).toEqual(3);
 
             expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin.js'))).toBe(true);
             expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin'))).toBe(true);
@@ -57,14 +56,16 @@ describe('install', function() {
             var sRemoveFile = spyOn(common, 'removeFile').andCallThrough();
             var sRemoveFileF = spyOn(common, 'removeFileF').andCallThrough();
             
-            // messing the plugin
-            shell.rm('-rf', path.join(plugins_dir, 'dummyplugin', 'www', 'dummyplugin')); 
+            // messing with the plugin
+            shell.mkdir('-p', plugins_dir);
+            shell.cp('-rf', dummyplugin, plugins_dir);
+            shell.rm('-rf', path.join(plugins_dir, 'DummyPlugin', 'www', 'dummyplugin')); 
             expect(function() {
-                install('android', temp, 'DummyPlugin', plugins_dir, {});
+                install('android', temp, 'DummyPlugin', plugins_dir, '.', {});
             }).toThrow();
             // making sure the right methods were called
             expect(sCopyFile).toHaveBeenCalled();
-            expect(sCopyFile.calls.length).toEqual(2);
+            expect(sCopyFile.calls.length).toEqual(3);
 
             expect(sRemoveFile).toHaveBeenCalled();
             expect(sRemoveFile.calls.length).toEqual(1);
@@ -77,10 +78,10 @@ describe('install', function() {
 
         it('should properly install assets into a custom www dir', function() {
             var s = spyOn(common, 'copyFile').andCallThrough();
-            install('android', temp, 'DummyPlugin', plugins_dir, {}, path.join(temp, 'staging'));
+            install('android', temp, dummyplugin, plugins_dir, '.', {}, path.join(temp, 'staging'));
             // making sure the right methods were called
             expect(s).toHaveBeenCalled();
-            expect(s.calls.length).toEqual(2);
+            expect(s.calls.length).toEqual(3);
 
             expect(fs.existsSync(path.join(temp, 'staging', 'dummyplugin.js'))).toBe(true);
             expect(fs.existsSync(path.join(temp, 'staging', 'dummyplugin'))).toBe(true);
@@ -96,13 +97,15 @@ describe('install', function() {
             var sRemoveFileF = spyOn(common, 'removeFileF').andCallThrough();
             
             // messing the plugin
+            shell.mkdir('-p', plugins_dir);
+            shell.cp('-rf', dummyplugin, plugins_dir);
             shell.rm('-rf', path.join(plugins_dir, 'dummyplugin', 'www', 'dummyplugin')); 
             expect(function() {
-                install('android', temp, 'DummyPlugin', plugins_dir, {}, path.join(temp, 'staging'));
+                install('android', temp, 'DummyPlugin', plugins_dir, '.', {}, path.join(temp, 'staging'));
             }).toThrow();
             // making sure the right methods were called
             expect(sCopyFile).toHaveBeenCalled();
-            expect(sCopyFile.calls.length).toEqual(2);
+            expect(sCopyFile.calls.length).toEqual(3);
 
             expect(sRemoveFile).toHaveBeenCalled();
             expect(sRemoveFile.calls.length).toEqual(1);
@@ -115,38 +118,56 @@ describe('install', function() {
 
         it('should call prepare after a successful install', function() {
             var s = spyOn(plugman, 'prepare');
-            install('android', temp, 'DummyPlugin', plugins_dir, {});
-            android_installer.mostRecentCall.args[5](); // fake the installer calling back successfully
+            install('android', temp, dummyplugin, plugins_dir, '.', {});
             expect(s).toHaveBeenCalled();
         });
 
         it('should call fetch if provided plugin cannot be resolved locally', function() {
             var s = spyOn(plugman, 'fetch');
-            install('android', temp, 'CLEANYOURSHORTS', plugins_dir, {});
+            install('android', temp, 'CLEANYOURSHORTS', plugins_dir, '.', {});
             expect(s).toHaveBeenCalled();
         });
-        it('should generate an array of transactions required to run an installation and pass into appropriate platform handler\'s install method', function() {
-            install('android', temp, 'DummyPlugin', plugins_dir, {});
-            var transactions = android_installer.mostRecentCall.args[0];
-
-            expect(transactions.length).toEqual(1);
-            expect(transactions[0].tag).toBe('source-file');
-        });
         it('should call the config-changes module\'s add_installed_plugin_to_prepare_queue method', function() {
-            install('android', temp, 'DummyPlugin', plugins_dir, {});
             var spy = spyOn(config_changes, 'add_installed_plugin_to_prepare_queue');
-            android_installer.mostRecentCall.args[5](null); // fake out handler install callback
-            expect(spy).toHaveBeenCalledWith(plugins_dir, 'DummyPlugin', 'android', {});
+            install('android', temp, dummyplugin, plugins_dir, '.', {});
+            expect(spy).toHaveBeenCalledWith(plugins_dir, dummy_id, 'android', {}, true);
+        });
+        it('should notify if plugin is already installed into project', function() {
+            expect(function() {
+                install('android', temp, dummyplugin, plugins_dir,'.',  {});
+            }).not.toThrow();
+            var spy = spyOn(console, 'log');
+            install('android', temp, dummyplugin, plugins_dir, '.', {});
+            expect(spy).toHaveBeenCalledWith('Plugin "com.phonegap.plugins.dummyplugin" already installed, \'sall good.');
+        });
+
+        describe('with dependencies', function() {
+            it('should process all dependent plugins', function() {
+                var spy = spyOn(actions.prototype, 'process').andCallThrough();
+                shell.mkdir('-p', plugins_dir);
+                shell.cp('-rf', dep_a, plugins_dir);
+                shell.cp('-rf', dep_d, plugins_dir);
+                shell.cp('-rf', dep_c, plugins_dir);
+                install('android', temp, 'A', plugins_dir, '.', {});
+                expect(spy.calls.length).toEqual(3);
+            });
+            it('should fetch any dependent plugins if missing', function() {
+                var spy = spyOn(plugman, 'fetch');
+                shell.mkdir('-p', plugins_dir);
+                shell.cp('-rf', dep_a, plugins_dir);
+                shell.cp('-rf', dep_c, plugins_dir);
+                install('android', temp, 'A', plugins_dir, '.', {});
+                expect(spy).toHaveBeenCalled();
+            });
         });
     });
 
     describe('failure', function() {
         it('should throw if asset target already exists', function() {
-            shell.cp('-rf', dummyplugin, plugins_dir);
             var target = path.join(temp, 'assets', 'www', 'dummyplugin.js');
             fs.writeFileSync(target, 'some bs', 'utf-8');
             expect(function() {
-                install('android', temp, 'DummyPlugin', plugins_dir, {});
+                install('android', temp, dummyplugin, plugins_dir, '.', {});
             }).toThrow();
         });
         it('should throw if platform is unrecognized', function() {
@@ -155,44 +176,28 @@ describe('install', function() {
             }).toThrow('atari not supported.');
         });
         it('should throw if variables are missing', function() {
-            shell.cp('-rf', variableplugin, plugins_dir);
             expect(function() {
-                install('android', temp, 'VariablePlugin', plugins_dir, {});
+                install('android', temp, variableplugin, plugins_dir, '.', {});
             }).toThrow('Variable(s) missing: API_KEY');
         });
-        it('should handle a failed install by passing completed transactions into appropriate handler\'s uninstall method', function() {
-            shell.cp('-rf', faultyplugin, plugins_dir);
-            var s = spyOn(android, 'uninstall');
-            install('android', temp, 'FaultyPlugin', plugins_dir, {});
-
-            var executed_txs = s.mostRecentCall.args[0];
-            expect(executed_txs.length).toEqual(0);
-        }); 
-        it('should throw if plugin is already installed into project', function() {
-            // TODO: plugins and their version can be recognized using the platform.json file
+        it('should throw if a file required for installation cannot be found', function() {
+            shell.mkdir('-p', plugins_dir);
             shell.cp('-rf', dummyplugin, plugins_dir);
+            shell.rm(path.join(plugins_dir, 'DummyPlugin', 'src', 'android', 'DummyPlugin.java')); 
+            
             expect(function() {
-                install('android', temp, 'DummyPlugin', plugins_dir, {});
-            }).not.toThrow();
-            expect(function() {
-                install('android', temp, 'DummyPlugin', plugins_dir, {});
+                install('android', temp, 'DummyPlugin', plugins_dir, '.', {});
             }).toThrow();
         });
-        it('should revert web assets if an install error occurs', function() {
-            var sRemoveFile = spyOn(common, 'removeFile').andCallThrough();
-            var sRemoveFileF = spyOn(common, 'removeFileF').andCallThrough();
+        it('should pass error into specified callback if a file required for installation cannot be found', function(done) {
+            shell.mkdir('-p', plugins_dir);
             shell.cp('-rf', dummyplugin, plugins_dir);
             shell.rm(path.join(plugins_dir, 'DummyPlugin', 'src', 'android', 'DummyPlugin.java')); 
             
-            install('android', temp, 'DummyPlugin', plugins_dir, {}, undefined, function() {});
-            
-            expect(sRemoveFile).toHaveBeenCalled();
-            expect(sRemoveFile.calls.length).toEqual(2);
-            expect(sRemoveFileF).toHaveBeenCalled();
-            expect(sRemoveFileF.calls.length).toEqual(1);
-           
-            expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin.js'))).toBe(false);
-            expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin'))).toBe(false);
+            install('android', temp, 'DummyPlugin', plugins_dir, '.', {}, null, function(err) {
+                expect(err).toBeDefined();
+                done();
+            });
         });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/A/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/A/plugin.xml b/spec/plugins/dependencies/A/plugin.xml
new file mode 100644
index 0000000..604f191
--- /dev/null
+++ b/spec/plugins/dependencies/A/plugin.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="A"
+    version="0.6.0">
+
+    <name>Plugin A</name>
+
+    <dependency id="C" />
+    <dependency id="D" url="D" />
+
+    <asset src="www/plugin-a.js" target="plugin-a.js" />
+
+    <config-file target="config.xml" parent="/*">
+        <access origin="build.phonegap.com" />
+    </config-file>
+	
+    <!-- android -->
+    <platform name="android">
+        <config-file target="res/xml/config.xml" parent="plugins">
+            <plugin name="A"
+                value="com.phonegap.A.A"/>
+        </config-file>
+
+        <source-file src="src/android/A.java"
+                target-dir="src/com/phonegap/A" />
+    </platform>
+
+        
+    <!-- ios -->
+    <platform name="ios">
+        <!-- CDV 2.5+ -->
+        <config-file target="config.xml" parent="plugins">
+            <plugin name="A"
+                value="APluginCommand"/>
+        </config-file>
+
+        <header-file src="src/ios/APluginCommand.h" />
+        <source-file src="src/ios/APluginCommand.m"/>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/A/src/android/A.java
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/A/src/android/A.java b/spec/plugins/dependencies/A/src/android/A.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/A/src/ios/APluginCommand.h
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/A/src/ios/APluginCommand.h b/spec/plugins/dependencies/A/src/ios/APluginCommand.h
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/A/src/ios/APluginCommand.m
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/A/src/ios/APluginCommand.m b/spec/plugins/dependencies/A/src/ios/APluginCommand.m
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/A/www/plugin-a.js
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/A/www/plugin-a.js b/spec/plugins/dependencies/A/www/plugin-a.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/B/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/B/plugin.xml b/spec/plugins/dependencies/B/plugin.xml
new file mode 100644
index 0000000..6f92495
--- /dev/null
+++ b/spec/plugins/dependencies/B/plugin.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="B"
+    version="0.6.0">
+
+    <name>Plugin B</name>
+
+    <dependency id="D" />
+    <dependency id="E" />
+
+    <asset src="www/plugin-b.js" target="plugin-b.js" />
+
+    <config-file target="config.xml" parent="/*">
+        <access origin="build.phonegap.com" />
+    </config-file>
+	
+    <!-- android -->
+    <platform name="android">
+        <config-file target="res/xml/config.xml" parent="plugins">
+            <plugin name="B"
+                value="com.phonegap.B.B"/>
+        </config-file>
+
+        <source-file src="src/android/B.java"
+                target-dir="src/com/phonegap/B" />
+    </platform>
+
+        
+    <!-- ios -->
+    <platform name="ios">
+        <!-- CDV 2.5+ -->
+        <config-file target="config.xml" parent="plugins">
+            <plugin name="B"
+                value="BPluginCommand"/>
+        </config-file>
+
+        <header-file src="src/ios/BPluginCommand.h" />
+        <source-file src="src/ios/BPluginCommand.m"/>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/B/src/android/B.java
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/B/src/android/B.java b/spec/plugins/dependencies/B/src/android/B.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/B/src/ios/BPluginCommand.h
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/B/src/ios/BPluginCommand.h b/spec/plugins/dependencies/B/src/ios/BPluginCommand.h
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/B/src/ios/BPluginCommand.m
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/B/src/ios/BPluginCommand.m b/spec/plugins/dependencies/B/src/ios/BPluginCommand.m
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/B/www/plugin-b.js
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/B/www/plugin-b.js b/spec/plugins/dependencies/B/www/plugin-b.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/C/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/C/plugin.xml b/spec/plugins/dependencies/C/plugin.xml
new file mode 100644
index 0000000..5baf559
--- /dev/null
+++ b/spec/plugins/dependencies/C/plugin.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="C"
+    version="0.6.0">
+
+    <name>Plugin C</name>
+
+    <asset src="www/plugin-c.js" target="plugin-c.js" />
+
+    <config-file target="config.xml" parent="/*">
+        <access origin="build.phonegap.com" />
+    </config-file>
+	
+    <!-- android -->
+    <platform name="android">
+        <config-file target="res/xml/config.xml" parent="plugins">
+            <plugin name="C"
+                value="com.phonegap.C.C"/>
+        </config-file>
+
+        <source-file src="src/android/C.java"
+                target-dir="src/com/phonegap/C" />
+    </platform>
+
+        
+    <!-- ios -->
+    <platform name="ios">
+        <!-- CDV 2.5+ -->
+        <config-file target="config.xml" parent="plugins">
+            <plugin name="C"
+                value="CPluginCommand"/>
+        </config-file>
+
+        <header-file src="src/ios/CPluginCommand.h" />
+        <source-file src="src/ios/CPluginCommand.m"/>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/C/src/android/C.java
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/C/src/android/C.java b/spec/plugins/dependencies/C/src/android/C.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/C/src/ios/CPluginCommand.h
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/C/src/ios/CPluginCommand.h b/spec/plugins/dependencies/C/src/ios/CPluginCommand.h
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/C/src/ios/CPluginCommand.m
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/C/src/ios/CPluginCommand.m b/spec/plugins/dependencies/C/src/ios/CPluginCommand.m
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/C/www/plugin-c.js
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/C/www/plugin-c.js b/spec/plugins/dependencies/C/www/plugin-c.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/D/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/D/plugin.xml b/spec/plugins/dependencies/D/plugin.xml
new file mode 100644
index 0000000..94449ef
--- /dev/null
+++ b/spec/plugins/dependencies/D/plugin.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="D"
+    version="0.6.0">
+
+    <name>Plugin D</name>
+
+    <asset src="www/plugin-d.js" target="plugin-d.js" />
+
+    <config-file target="config.xml" parent="/*">
+        <access origin="build.phonegap.com" />
+    </config-file>
+	
+    <!-- android -->
+    <platform name="android">
+        <config-file target="res/xml/config.xml" parent="plugins">
+            <plugin name="D"
+                value="com.phonegap.D.D"/>
+        </config-file>
+
+        <source-file src="src/android/D.java"
+                target-dir="src/com/phonegap/D" />
+    </platform>
+
+        
+    <!-- ios -->
+    <platform name="ios">
+        <!-- CDV 2.5+ -->
+        <config-file target="config.xml" parent="plugins">
+            <plugin name="D"
+                value="DPluginCommand"/>
+        </config-file>
+
+        <header-file src="src/ios/DPluginCommand.h" />
+        <source-file src="src/ios/DPluginCommand.m"/>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/D/src/android/D.java
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/D/src/android/D.java b/spec/plugins/dependencies/D/src/android/D.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/D/src/ios/DPluginCommand.h
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/D/src/ios/DPluginCommand.h b/spec/plugins/dependencies/D/src/ios/DPluginCommand.h
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/D/src/ios/DPluginCommand.m
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/D/src/ios/DPluginCommand.m b/spec/plugins/dependencies/D/src/ios/DPluginCommand.m
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/D/www/plugin-d.js
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/D/www/plugin-d.js b/spec/plugins/dependencies/D/www/plugin-d.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/E/plugin.xml
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/E/plugin.xml b/spec/plugins/dependencies/E/plugin.xml
new file mode 100644
index 0000000..2564e96
--- /dev/null
+++ b/spec/plugins/dependencies/E/plugin.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2013 Anis Kadri
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+
+<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    id="E"
+    version="0.6.0">
+
+    <name>Plugin E</name>
+
+    <asset src="www/plugin-e.js" target="plugin-e.js" />
+
+    <config-file target="config.xml" parent="/*">
+        <access origin="build.phonegap.com" />
+    </config-file>
+	
+    <!-- android -->
+    <platform name="android">
+        <config-file target="res/xml/config.xml" parent="plugins">
+            <plugin name="E"
+                value="com.phonegap.E.E"/>
+        </config-file>
+
+        <source-file src="src/android/E.java"
+                target-dir="src/com/phonegap/E" />
+    </platform>
+
+        
+    <!-- ios -->
+    <platform name="ios">
+        <!-- CDV 2.5+ -->
+        <config-file target="config.xml" parent="plugins">
+            <plugin name="E"
+                value="EPluginCommand"/>
+        </config-file>
+
+        <header-file src="src/ios/EPluginCommand.h" />
+        <source-file src="src/ios/EPluginCommand.m"/>
+    </platform>
+</plugin>

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/E/src/android/E.java
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/E/src/android/E.java b/spec/plugins/dependencies/E/src/android/E.java
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/E/src/ios/EPluginCommand.h
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/E/src/ios/EPluginCommand.h b/spec/plugins/dependencies/E/src/ios/EPluginCommand.h
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/E/src/ios/EPluginCommand.m
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/E/src/ios/EPluginCommand.m b/spec/plugins/dependencies/E/src/ios/EPluginCommand.m
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/E/www/plugin-e.js
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/E/www/plugin-e.js b/spec/plugins/dependencies/E/www/plugin-e.js
new file mode 100644
index 0000000..e69de29

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/plugins/dependencies/README.md
----------------------------------------------------------------------
diff --git a/spec/plugins/dependencies/README.md b/spec/plugins/dependencies/README.md
new file mode 100644
index 0000000..29cc70f
--- /dev/null
+++ b/spec/plugins/dependencies/README.md
@@ -0,0 +1,5 @@
+Here's a general overview of how the plugins in this directory are dependent on each other:
+
+        A          B
+       / \        / \
+      C   '---D--'   E

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/spec/uninstall.spec.js
----------------------------------------------------------------------
diff --git a/spec/uninstall.spec.js b/spec/uninstall.spec.js
index c39812a..99d6026 100644
--- a/spec/uninstall.spec.js
+++ b/spec/uninstall.spec.js
@@ -1,6 +1,7 @@
 var uninstall = require('../src/uninstall'),
     install = require('../src/install'),
     common = require('../src/platforms/common'),
+    actions = require('../src/util/action-stack'),
     android = require('../src/platforms/android'),
     ios     = require('../src/platforms/ios'),
     blackberry = require('../src/platforms/blackberry'),
@@ -14,41 +15,44 @@ var uninstall = require('../src/uninstall'),
     shell   = require('shelljs'),
     temp    = path.join(os.tmpdir(), 'plugman'),
     dummyplugin = path.join(__dirname, 'plugins', 'DummyPlugin'),
+    dummy_id = 'com.phonegap.plugins.dummyplugin',
     faultyplugin = path.join(__dirname, 'plugins', 'FaultyPlugin'),
     childbrowserplugin = path.join(__dirname, 'plugins', 'ChildBrowser'),
+    dep_a = path.join(__dirname, 'plugins', 'dependencies', 'A'),
+    dep_b = path.join(__dirname, 'plugins', 'dependencies', 'B'),
+    dep_c = path.join(__dirname, 'plugins', 'dependencies', 'C'),
+    dep_d = path.join(__dirname, 'plugins', 'dependencies', 'D'),
+    dep_e = path.join(__dirname, 'plugins', 'dependencies', 'E'),
     android_one_project = path.join(__dirname, 'projects', 'android_one', '*'),
     ios_project = path.join(__dirname, 'projects', 'ios-config-xml', '*'),
     plugins_dir = path.join(temp, 'cordova', 'plugins');
 
 describe('uninstall', function() {
     var copied_plugin_path = path.join(temp,'ChildBrowser');
+    var dummy_plugin_path = path.join(plugins_dir, dummy_id);
 
     beforeEach(function() {
         shell.mkdir('-p', temp);
-        shell.mkdir('-p', plugins_dir);
         shell.cp('-rf', android_one_project, temp);
-        shell.cp('-rf', dummyplugin, plugins_dir);
     });
     afterEach(function() {
         shell.rm('-rf', temp);
     });
 
     describe('success', function() {
-        var android_uninstaller;
         beforeEach(function() {
-            install('android', temp, 'DummyPlugin', plugins_dir, {});
-            android_uninstaller = spyOn(android, 'uninstall');
+            install('android', temp, dummyplugin, plugins_dir, '.', {});
         });
         it('should properly uninstall assets', function() {
             var s = spyOn(common, 'removeFile').andCallThrough();
             var s2 = spyOn(common, 'removeFileF').andCallThrough();
             // making sure the right methods were called
-            uninstall('android', temp, 'DummyPlugin', plugins_dir, {});
+            uninstall('android', temp, dummy_id, plugins_dir, {});
             expect(s).toHaveBeenCalled();
             expect(s.calls.length).toEqual(2);
             
             expect(s2).toHaveBeenCalled();
-            expect(s2.calls.length).toEqual(1);
+            expect(s2.calls.length).toEqual(2);
 
             expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin.js'))).toBe(false);
             expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin'))).toBe(false);
@@ -61,28 +65,46 @@ describe('uninstall', function() {
             shell.rm('-rf', path.join(temp, 'assets', 'www', 'dummyplugin'));
             
             expect(function() {
-                uninstall('android', temp, 'DummyPlugin', plugins_dir, {});
+                uninstall('android', temp, dummy_id, plugins_dir, {});
             }).toThrow();
 
             expect(sRemoveFile).toHaveBeenCalled();
             expect(sRemoveFile.calls.length).toEqual(2);
             expect(sCopyFile).toHaveBeenCalled();
-            expect(sCopyFile.calls.length).toEqual(1);
+            expect(sCopyFile.calls.length).toEqual(2);
             
             expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin.js'))).toBe(true);
         });
-        it('should generate and pass uninstall transaction log to appropriate platform handler\'s uninstall', function() {
-            uninstall('android', temp, 'DummyPlugin', plugins_dir, {});
-            var transactions = android_uninstaller.mostRecentCall.args[0];
-
-            expect(transactions.length).toEqual(1);
-            expect(transactions[0].tag).toBe('source-file');
-        });
         it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method', function() {
-            uninstall('android', temp, 'DummyPlugin', plugins_dir, {});
             var spy = spyOn(config_changes, 'add_uninstalled_plugin_to_prepare_queue');
-            android_uninstaller.mostRecentCall.args[4](null); // fake out handler uninstall callback
-            expect(spy).toHaveBeenCalledWith(plugins_dir, 'DummyPlugin', 'android');
+            uninstall('android', temp, dummy_id, plugins_dir, {});
+            expect(spy).toHaveBeenCalledWith(plugins_dir, dummy_id, 'android', true);
+        });
+
+        describe('with dependencies', function() {
+            it('should uninstall any dependent plugins', function() {
+                shell.mkdir('-p', plugins_dir);
+                shell.cp('-rf', dep_a, plugins_dir);
+                shell.cp('-rf', dep_d, plugins_dir);
+                shell.cp('-rf', dep_c, plugins_dir);
+                install('android', temp, 'A', plugins_dir, '.', {});
+                var spy = spyOn(actions.prototype, 'process').andCallThrough();
+                uninstall('android', temp, 'A', plugins_dir, {});
+                expect(spy.calls.length).toEqual(3);
+            });
+            it('should not uninstall any dependent plugins that are required by other top-level plugins', function() {
+                shell.mkdir('-p', plugins_dir);
+                shell.cp('-rf', dep_a, plugins_dir);
+                shell.cp('-rf', dep_b, plugins_dir);
+                shell.cp('-rf', dep_d, plugins_dir);
+                shell.cp('-rf', dep_c, plugins_dir);
+                shell.cp('-rf', dep_e, plugins_dir);
+                install('android', temp, 'A', plugins_dir, '.', {});
+                install('android', temp, 'B', plugins_dir, '.', {});
+                var spy = spyOn(actions.prototype, 'process').andCallThrough();
+                uninstall('android', temp, 'A', plugins_dir, {});
+                expect(spy.calls.length).toEqual(2);
+            });
         });
     });
 
@@ -97,37 +119,5 @@ describe('uninstall', function() {
                 uninstall('android', temp, 'SomePlugin', plugins_dir, {});
             }).toThrow('Plugin "SomePlugin" not found. Already uninstalled?');
         });
-       // it('should handle a failed uninstall by passing completed transactions into appropriate handler\'s install method', function() {
-       //     shell.rm('-rf', path.join(temp, '*'));
-       //     shell.mkdir('-p', plugins_dir);
-       //     
-       //     shell.cp('-rf', ios_project, temp);
-       //     shell.cp('-rf', childbrowserplugin, plugins_dir);
-       //     install('ios', temp, 'ChildBrowser', plugins_dir, {});
-
-       //     // make uninstall fail by removing a js asset
-       //     shell.rm(path.join(temp, 'SampleApp', 'Plugins', 'ChildBrowserCommand.m'));
-       //     var s = spyOn(ios, 'install');
-       //     uninstall('ios', temp, 'ChildBrowser', plugins_dir, {});
-       //     var executed_txs = s.mostRecentCall.args[0];
-       //     expect(executed_txs.length).toEqual(1);
-       //     // It only ended up "uninstalling" one source file, so install reversion should pass in that source file to re-install
-       //     expect(executed_txs[0].tag).toEqual('source-file');
-       // });
-        it('should revert assets when uninstall fails', function() {
-            install('android', temp, 'DummyPlugin', plugins_dir, {});
-            
-            var s = spyOn(common, 'copyFile').andCallThrough();
-            
-            shell.rm('-rf', path.join(temp, 'src', 'com', 'phonegap', 'plugins', 'dummyplugin'));
-            expect(function() {
-                uninstall('android', temp, 'DummyPlugin', plugins_dir, {});
-            }).toThrow();
-            expect(s).toHaveBeenCalled();
-            expect(s.calls.length).toEqual(2);
-            
-            expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin.js'))).toBe(true);
-            expect(fs.existsSync(path.join(temp, 'assets', 'www', 'dummyplugin'))).toBe(true);
-        });
     });
 });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 37a6dff..9a85e2c 100644
--- a/src/install.js
+++ b/src/install.js
@@ -6,7 +6,7 @@ var path = require('path'),
     action_stack = require('./util/action-stack'),
     platform_modules = require('./platforms');
 
-module.exports = function installPlugin(platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, is_top_level, callback) {
+module.exports = function installPlugin(platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, callback) {
     if (!platform_modules[platform]) {
         var err = new Error(platform + " not supported.");
         if (callback) callback(err);
@@ -14,6 +14,12 @@ module.exports = function installPlugin(platform, project_dir, id, plugins_dir,
         return;
     }
 
+    var current_stack = new action_stack();
+
+    possiblyFetch(current_stack, platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, true, callback);
+};
+
+function possiblyFetch(actions, platform, project_dir, id, plugins_dir, subdir, cli_variables, www_dir, is_top_level, callback) {
     var plugin_dir = path.join(plugins_dir, id);
 
     // Check that the plugin has already been fetched.
@@ -25,15 +31,15 @@ module.exports = function installPlugin(platform, project_dir, id, plugins_dir,
                 callback(err);
             } else {
                 // update ref to plugin_dir after successful fetch, via fetch callback
-                runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
+                runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
             }
         });
     } else {
-        runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
+        runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
     }
-};
+}
 
-function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
+function runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
     var xml_path     = path.join(plugin_dir, 'plugin.xml')
       , xml_text     = fs.readFileSync(xml_path, 'utf-8')
       , plugin_et    = new et.ElementTree(et.XML(xml_text))
@@ -83,7 +89,7 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
     var dependencies = plugin_et.findall('dependency');
     if (dependencies && dependencies.length) {
         var end = n(dependencies.length, function() {
-            handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback);
+            handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback);
         });
         dependencies.forEach(function(dep) {
             var dep_plugin_id = dep.attrib.id;
@@ -92,21 +98,21 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
             if (dep_subdir) {
                 dep_subdir = path.join.apply(null, dep_subdir.split('/'));
             }
-
-            if (fs.existsSync(path.join(plugins_dir, dep_plugin_id))) {
-                console.log('Dependent plugin ' + dep.attrib.id + ' already fetched, using that version.');
-                module.exports(platform, project_dir, dep_plugin_id, plugins_dir, dep_subdir, filtered_variables, www_dir, false, end);
+            var dep_plugin_dir = path.join(plugins_dir, dep_plugin_id);
+            if (fs.existsSync(dep_plugin_dir)) {
+                console.log('Dependent plugin ' + dep_plugin_id + ' already fetched, using that version.');
+                runInstall(actions, platform, project_dir, dep_plugin_dir, plugins_dir, filtered_variables, www_dir, false, end);
             } else {
-                console.log('Dependent plugin ' + dep.attrib.id + ' not fetched, retrieving then installing.');
-                module.exports(platform, project_dir, dep_url, plugins_dir, dep_subdir, filtered_variables, www_dir, false, end);
+                console.log('Dependent plugin ' + dep_plugin_id + ' not fetched, retrieving then installing.');
+                possiblyFetch(actions, platform, project_dir, dep_url, plugins_dir, dep_subdir, filtered_variables, www_dir, false, end);
             }
         });
     } else {
-        handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback);
+        handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback);
     }
 }
 
-function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback) {
+function handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level, callback) {
     var handler = platform_modules[platform];
     www_dir = www_dir || handler.www_dir(project_dir);
 
@@ -121,30 +127,30 @@ function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir,
 
         // queue up native stuff
         sourceFiles && sourceFiles.forEach(function(source) {
-            action_stack.push(action_stack.createAction(handler["source-file"].install, [source, plugin_dir, project_dir], handler["source-file"].uninstall, [source, project_dir]));
+            actions.push(actions.createAction(handler["source-file"].install, [source, plugin_dir, project_dir], handler["source-file"].uninstall, [source, project_dir]));
         });
 
         headerFiles && headerFiles.forEach(function(header) {
-            action_stack.push(action_stack.createAction(handler["header-file"].install, [header, plugin_dir, project_dir], handler["header-file"].uninstall, [header, project_dir]));
+            actions.push(actions.createAction(handler["header-file"].install, [header, plugin_dir, project_dir], handler["header-file"].uninstall, [header, project_dir]));
         });
 
         resourceFiles && resourceFiles.forEach(function(resource) {
-            action_stack.push(action_stack.createAction(handler["resource-file"].install, [resource, plugin_dir, project_dir], handler["resource-file"].uninstall, [resource, project_dir]));
+            actions.push(actions.createAction(handler["resource-file"].install, [resource, plugin_dir, project_dir], handler["resource-file"].uninstall, [resource, project_dir]));
         });
 
         frameworks && frameworks.forEach(function(framework) {
-            action_stack.push(action_stack.createAction(handler["framework"].install, [framework, plugin_dir, project_dir], handler["framework"].uninstall, [framework, project_dir]));
+            actions.push(actions.createAction(handler["framework"].install, [framework, plugin_dir, project_dir], handler["framework"].uninstall, [framework, project_dir]));
         });
     }
 
     // queue up asset installation
     var common = require('./platforms/common');
     assets && assets.forEach(function(asset) {
-        action_stack.push(action_stack.createAction(common.asset.install, [asset, plugin_dir, www_dir], common.asset.uninstall, [asset, www_dir, plugin_id]));
+        actions.push(actions.createAction(common.asset.install, [asset, plugin_dir, www_dir], common.asset.uninstall, [asset, www_dir, plugin_id]));
     });
 
     // run through the action stack
-    action_stack.process(platform, project_dir, function(err) {
+    actions.process(platform, project_dir, function(err) {
         if (err) {
             if (callback) callback(err);
             else throw err;

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/src/uninstall.js
----------------------------------------------------------------------
diff --git a/src/uninstall.js b/src/uninstall.js
index 986e7c1..7528876 100644
--- a/src/uninstall.js
+++ b/src/uninstall.js
@@ -9,7 +9,7 @@ var path = require('path'),
     underscore = require('underscore'),
     platform_modules = require('./platforms');
 
-module.exports = function uninstallPlugin(platform, project_dir, id, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
+module.exports = function uninstallPlugin(platform, project_dir, id, plugins_dir, cli_variables, www_dir, callback) {
     if (!platform_modules[platform]) {
         var err = new Error(platform + " not supported.");
         if (callback) callback(err);
@@ -20,16 +20,18 @@ module.exports = function uninstallPlugin(platform, project_dir, id, plugins_dir
     var plugin_dir = path.join(plugins_dir, id);
 
     if (!fs.existsSync(plugin_dir)) {
-        var err = new Error('Plugin "' + name + '" not found. Already uninstalled?');
+        var err = new Error('Plugin "' + id + '" not found. Already uninstalled?');
         if (callback) callback(err);
         else throw err;
         return;
     }
 
-    runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback);
+    var current_stack = new action_stack();
+
+    runUninstall(current_stack, platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, true, callback);
 };
 
-function runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
+function runUninstall(actions, platform, project_dir, plugin_dir, plugins_dir, cli_variables, www_dir, is_top_level, callback) {
     var xml_path     = path.join(plugin_dir, 'plugin.xml')
       , xml_text     = fs.readFileSync(xml_path, 'utf-8')
       , plugin_et    = new et.ElementTree(et.XML(xml_text))
@@ -60,18 +62,19 @@ function runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variab
     var danglers = underscore.difference.apply(null, diff_arr);
     if (dependents.length && danglers && danglers.length) {
         var end = n(danglers.length, function() {
-            handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
+            handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
         });
         danglers.forEach(function(dangler) {
-            module.exports(platform, project_dir, dangler, plugins_dir, cli_variables, www_dir, false /* TODO: should this "is_top_level" param be false for dependents? */, end);
+            var dependent_path = path.join(plugins_dir, dangler);
+            runUninstall(actions, platform, project_dir, dependent_path, plugins_dir, cli_variables, www_dir, false /* TODO: should this "is_top_level" param be false for dependents? */, end);
         });
     } else {
         // this plugin can get axed by itself, gogo!
-        handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
+        handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
     }
 }
 
-function handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback) {
+function handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback) {
     var platform_modules = require('./platforms');
     var handler = platform_modules[platform];
     var platformTag = plugin_et.find('./platform[@name="'+platform+'"]');
@@ -87,30 +90,30 @@ function handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, p
 
         // queue up native stuff
         sourceFiles && sourceFiles.forEach(function(source) {
-            action_stack.push(action_stack.createAction(handler["source-file"].uninstall, [source, project_dir], handler["source-file"].install, [source, plugin_dir, project_dir]));
+            actions.push(actions.createAction(handler["source-file"].uninstall, [source, project_dir], handler["source-file"].install, [source, plugin_dir, project_dir]));
         });
 
         headerFiles && headerFiles.forEach(function(header) {
-            action_stack.push(action_stack.createAction(handler["header-file"].uninstall, [header, project_dir], handler["header-file"].install, [header, plugin_dir, project_dir]));
+            actions.push(actions.createAction(handler["header-file"].uninstall, [header, project_dir], handler["header-file"].install, [header, plugin_dir, project_dir]));
         });
 
         resourceFiles && resourceFiles.forEach(function(resource) {
-            action_stack.push(action_stack.createAction(handler["resource-file"].uninstall, [resource, project_dir], handler["resource-file"].install, [resource, plugin_dir, project_dir]));
+            actions.push(actions.createAction(handler["resource-file"].uninstall, [resource, project_dir], handler["resource-file"].install, [resource, plugin_dir, project_dir]));
         });
 
         frameworks && frameworks.forEach(function(framework) {
-            action_stack.push(action_stack.createAction(handler["framework"].uninstall, [framework, project_dir], handler["framework"].install, [framework, plugin_dir, project_dir]));
+            actions.push(actions.createAction(handler["framework"].uninstall, [framework, project_dir], handler["framework"].install, [framework, plugin_dir, project_dir]));
         });
     }
 
     // queue up asset installation
     var common = require('./platforms/common');
     assets && assets.forEach(function(asset) {
-        action_stack.push(action_stack.createAction(common.asset.uninstall, [asset, www_dir, plugin_id], common.asset.install, [asset, plugin_dir, www_dir]));
+        actions.push(actions.createAction(common.asset.uninstall, [asset, www_dir, plugin_id], common.asset.install, [asset, plugin_dir, www_dir]));
     });
 
     // run through the action stack
-    action_stack.process(platform, project_dir, function(err) {
+    actions.process(platform, project_dir, function(err) {
         if (err) {
             if (callback) callback(err);
             else throw err;

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/bf56f3ed/src/util/action-stack.js
----------------------------------------------------------------------
diff --git a/src/util/action-stack.js b/src/util/action-stack.js
index a76f33c..115c6b7 100644
--- a/src/util/action-stack.js
+++ b/src/util/action-stack.js
@@ -1,10 +1,12 @@
 var ios = require('../platforms/ios'),
     fs = require('fs');
 
-var stack = [];
-var completed = [];
+function ActionStack() {
+    this.stack = [];
+    this.completed = [];
+}
 
-module.exports = {
+ActionStack.prototype = {
     createAction:function(handler, action_params, reverter, revert_params) {
         return {
             handler:{
@@ -18,26 +20,26 @@ module.exports = {
         };
     },
     push:function(tx) {
-        stack.push(tx);
+        this.stack.push(tx);
     },
     process:function(platform, project_dir, callback) {
         if (platform == 'ios') {
             // parse xcode project file once
             var project_files = ios.parseIOSProjectFiles(project_dir);
         }
-        while(stack.length) {
-            var action = stack.shift();
+        while(this.stack.length) {
+            var action = this.stack.shift();
             var handler = action.handler.run;
             var action_params = action.handler.params;
             if (platform == 'ios') action_params.push(project_files);
             try {
                 handler.apply(null, action_params);
             } catch(e) {
-                var incomplete = stack.unshift(action);
+                var incomplete = this.stack.unshift(action);
                 var issue = 'Uh oh!\n';
                 // revert completed tasks
-                while(completed.length) {
-                    var undo = completed.shift();
+                while(this.completed.length) {
+                    var undo = this.completed.shift();
                     var revert = undo.reverter.run;
                     var revert_params = undo.reverter.params;
                     if (platform == 'ios') revert_params.push(project_files);
@@ -52,7 +54,7 @@ module.exports = {
                 else throw e;
                 return;
             }
-            completed.push(action);
+            this.completed.push(action);
         }
         if (platform == 'ios') {
             // write out xcodeproj file
@@ -61,3 +63,5 @@ module.exports = {
         if (callback) callback();
     }
 };
+
+module.exports = ActionStack;


[08/20] git commit: First pass at dependency support.

Posted by fi...@apache.org.
First pass at dependency support.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/67f59c90
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/67f59c90
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/67f59c90

Branch: refs/heads/master
Commit: 67f59c907336f4a32232e975df0a2059b8e71f96
Parents: 1a6285b
Author: Braden Shepherdson <br...@gmail.com>
Authored: Thu May 9 14:38:46 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 src/install.js           |    4 +++
 src/util/dependencies.js |   55 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/67f59c90/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 257d8de..d7f8c46 100644
--- a/src/install.js
+++ b/src/install.js
@@ -80,6 +80,10 @@ function runInstall(platform, project_dir, plugin_dir, plugins_dir, cli_variable
         else throw err;
         return;
     }
+
+    // We need to install this plugin, so install its dependencies first.
+    dependencies.installAll(platform, project_dir, path.basename(plugin_dir), plugins_dir, cli_variables, www_dir, callback);
+
     // TODO: if plugin does not have platform tag but has platform-agnostic config changes, should we queue it up?
     var platformTag = plugin_et.find('./platform[@name="'+platform+'"]');
     if (!platformTag) {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/67f59c90/src/util/dependencies.js
----------------------------------------------------------------------
diff --git a/src/util/dependencies.js b/src/util/dependencies.js
new file mode 100644
index 0000000..381fdf0
--- /dev/null
+++ b/src/util/dependencies.js
@@ -0,0 +1,55 @@
+var install = require('../install'),
+    fetch   = require('../fetch'),
+    fs      = require('fs'),
+    xml_helpers = require('./xml-helpers');
+
+exports.installAll = function(platform, project_dir, name, plugins_dir, cli_variables, www_dir, callback) {
+    // It should have been fetched already, so read the plugin.xml from plugin_dir
+    var plugin_dir = path.join(plugins_dir, name);
+    var xml = xml_helpers.parseElementtreeSync(path.join(plugin_dir, 'plugin.xml'));
+
+    var dependencies = xml.findall('dependency');
+
+    if (!dependencies || dependencies.length == 0) {
+        return;
+    }
+
+    function waitForAll(n) {
+        var count = n;
+        var errs = [];
+        return function(err) {
+            count--;
+            if (err) {
+                throw err;
+            }
+            if (count == 0) {
+                callback();
+            }
+        };
+    }
+
+    var dependencyCallback = waitForAll(dependencies.length);
+
+    dependencies && dependencies.forEach(function(dep) {
+        function doInstall(plugin_dir) {
+            // Call installation for this plugin after it gets fetched.
+            install(platform, project_dir, path.basename(plugin_dir), plugins_dir, cli_variables, www_dir, dependencyCallback);
+        }
+
+        // Check if this dependency is already there.
+        if (fs.existsSync(path.join(plugins_dir, dep.attrib.id))) {
+            console.log('Plugin ' + dep.attrib.id + ' already fetched, using that version.');
+            doInstall(path.join(plugins_dir, dep.attrib.id));
+        } else {
+            // Fetch it.
+            var subdir = dep.attrib.subdir;
+            if (subdir) {
+                subdir = path.join.apply(null, dep.attrib.subdir.split('/'));
+            }
+
+            console.log('Fetching dependency ' + dep.attrib.id);
+            fetch(dep.attrib.url, plugins_dir, false /* no link */, subdir, doInstall);
+        }
+    });
+};
+


[20/20] git commit: version 0.7.0 after landing dependencies

Posted by fi...@apache.org.
version 0.7.0 after landing dependencies


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/0365cbf8
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/0365cbf8
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/0365cbf8

Branch: refs/heads/master
Commit: 0365cbf81b2848451faf5028b9f02fda193aff73
Parents: 617d776
Author: Fil Maj <ma...@gmail.com>
Authored: Thu May 16 08:56:40 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:56:40 2013 -0700

----------------------------------------------------------------------
 package.json |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/0365cbf8/package.json
----------------------------------------------------------------------
diff --git a/package.json b/package.json
index 73ab275..a68d17d 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "author": "Andrew Lunny <al...@gmail.com>",
   "name": "plugman",
   "description": "install/uninstall Cordova plugins",
-  "version": "0.6.3",
+  "version": "0.7.0",
   "repository": {
     "type": "git",
     "url": "git://git-wip-us.apache.org/repos/asf/cordova-plugman.git"


[14/20] git commit: fixed blackberry specs

Posted by fi...@apache.org.
fixed blackberry specs


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/4cb155cd
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/4cb155cd
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/4cb155cd

Branch: refs/heads/master
Commit: 4cb155cd9b029e307518082579b3c0e52371aad8
Parents: a99a9d3
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 15:31:19 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:43 2013 -0700

----------------------------------------------------------------------
 spec/platforms/blackberry.spec.js |   29 +++++++++++++++++------------
 1 files changed, 17 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/4cb155cd/spec/platforms/blackberry.spec.js
----------------------------------------------------------------------
diff --git a/spec/platforms/blackberry.spec.js b/spec/platforms/blackberry.spec.js
index c59af0c..9040b6f 100644
--- a/spec/platforms/blackberry.spec.js
+++ b/spec/platforms/blackberry.spec.js
@@ -37,14 +37,15 @@ function copyArray(arr) {
 }
 
 describe('blackberry project handler', function() {
-    it('should have an install function', function() {
-        expect(typeof blackberry.install).toEqual('function');
-    });
-    it('should have an uninstall function', function() {
-        expect(typeof blackberry.uninstall).toEqual('function');
+    describe('www_dir method', function() {
+        it('should return cordova-blackberry project www location using www_dir', function() {
+            expect(blackberry.www_dir('/')).toEqual('/www');
+        });
     });
-    it('should return cordova-blackberry project www location using www_dir', function() {
-        expect(blackberry.www_dir('/')).toEqual('/www');
+    describe('package_name method', function() {
+        it('should return the blackberry project package name based on what is in config.xml', function() {
+            expect(blackberry.package_name(path.join(blackberry_project, '..'))).toEqual('cordovaExample');
+        });
     });
 
     describe('installation', function() {
@@ -59,7 +60,9 @@ describe('blackberry project handler', function() {
             it('should copy stuff from one location to another by calling common.copyFile', function() {
                 var source = copyArray(valid_source);
                 var s = spyOn(common, 'copyFile');
-                blackberry.install(source, dummy_id, temp, dummyplugin, {});
+                source.forEach(function(src) {
+                    blackberry['source-file'].install(src, dummyplugin, temp);
+                });
                 expect(s).toHaveBeenCalledWith(dummyplugin, 'src/blackberry/client.js', temp, 'ext-qnx/cordova.echo/client.js');
                 expect(s).toHaveBeenCalledWith(dummyplugin, 'src/blackberry/index.js', temp, 'ext-qnx/cordova.echo/index.js');
                 expect(s).toHaveBeenCalledWith(dummyplugin, 'src/blackberry/manifest.json', temp, 'ext-qnx/cordova.echo/manifest.json');
@@ -67,7 +70,7 @@ describe('blackberry project handler', function() {
             it('should throw if source file cannot be found', function() {
                 var source = copyArray(invalid_source);
                 expect(function() {
-                    blackberry.install(source, faulty_id, temp, faultyplugin, {});
+                    blackberry['source-file'].install(source[1], faultyplugin, temp);
                 }).toThrow('"' + path.resolve(faultyplugin, 'src/blackberry/device/echoJnext.so') + '" not found!');
             });
             it('should throw if target file already exists', function() {
@@ -79,7 +82,7 @@ describe('blackberry project handler', function() {
 
                 var source = copyArray(valid_source);
                 expect(function() {
-                    blackberry.install(source, dummy_id, temp, dummyplugin, {});
+                    blackberry['source-file'].install(source[0], dummyplugin, temp);
                 }).toThrow('"' + target + '" already exists!');
             });
         });
@@ -98,9 +101,11 @@ describe('blackberry project handler', function() {
         describe('of <source-file> elements', function() {
             it('should remove stuff by calling common.deleteJava', function(done) {
                 var s = spyOn(common, 'deleteJava');
-                install('blackberry', temp, 'DummyPlugin', plugins_dir, {}, undefined, function() {
+                install('blackberry', temp, 'DummyPlugin', plugins_dir, '.', {}, undefined, function() {
                     var source = copyArray(valid_source);
-                    blackberry.uninstall(source, dummy_id, temp, path.join(plugins_dir, 'DummyPlugin'));
+                    source.forEach(function(src) {
+                        blackberry['source-file'].uninstall(src, temp);
+                    });
                     expect(s).toHaveBeenCalledWith(temp, 'ext-qnx/cordova.echo/client.js');
                     expect(s).toHaveBeenCalledWith(temp, 'ext-qnx/cordova.echo/index.js');
                     expect(s).toHaveBeenCalledWith(temp, 'ext-qnx/cordova.echo/manifest.json');


[13/20] git commit: uninstallation with dependents should now work.

Posted by fi...@apache.org.
uninstallation with dependents should now work.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/e05e51ac
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/e05e51ac
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/e05e51ac

Branch: refs/heads/master
Commit: e05e51acbbd149a2e4288436e923690486491a4d
Parents: be96c19
Author: Fil Maj <ma...@gmail.com>
Authored: Tue May 14 23:50:43 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:42 2013 -0700

----------------------------------------------------------------------
 src/install.js   |    5 +++--
 src/uninstall.js |   34 ++++++++++++++++++++--------------
 2 files changed, 23 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/e05e51ac/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 24f012d..37a6dff 100644
--- a/src/install.js
+++ b/src/install.js
@@ -146,8 +146,8 @@ function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir,
     // run through the action stack
     action_stack.process(platform, project_dir, function(err) {
         if (err) {
-            console.error(err.message, err.stack);
-            console.error('Plugin installation failed :(');
+            if (callback) callback(err);
+            else throw err;
         } else {
             // WIN!
             // Log out plugin INFO element contents in case additional install steps are necessary
@@ -161,6 +161,7 @@ function handleInstall(plugin_id, plugin_et, platform, project_dir, plugins_dir,
             // call prepare after a successful install
             require('./../plugman').prepare(project_dir, platform, plugins_dir);
 
+            console.log(plugin_id + ' installed.');
             if (callback) callback();
         }
     });

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/e05e51ac/src/uninstall.js
----------------------------------------------------------------------
diff --git a/src/uninstall.js b/src/uninstall.js
index ee0cd62..986e7c1 100644
--- a/src/uninstall.js
+++ b/src/uninstall.js
@@ -1,8 +1,10 @@
 var path = require('path'),
     fs   = require('fs'),
     et   = require('elementtree'),
+    shell= require('shelljs'),
     config_changes = require('./util/config-changes'),
     action_stack = require('./util/action-stack'),
+    n = require('ncallbacks'),
     dependencies = require('./util/dependencies'),
     underscore = require('underscore'),
     platform_modules = require('./platforms');
@@ -44,26 +46,27 @@ function runUninstall(platform, project_dir, plugin_dir, plugins_dir, cli_variab
         if (tlp != plugin_id) {
             var ds = graph.getChain(tlp);
             if (is_top_level && ds.indexOf(plugin_id) > -1) {
-                // Another top-level plugin depends on this one
-                // Cannot uninstall then.
-                console.log('Another top-level plugin (' + tlp + ') relies on plugin ' + plugin_id + ', therefore, aborting uninstallation.');
-                if (callback) callback();
+                var err = new Error('Another top-level plugin (' + tlp + ') relies on plugin ' + plugin_id + ', therefore aborting uninstallation.');
+                if (callback) callback(err);
+                else throw err;
                 return;
             }
             diff_arr.push(ds);
         }
     });
 
-    if (dependents.length) {
-        // this plugin has dependencies
-        diff_arr.unshift(dependents);
-        // do a set difference to determine which dependencies are not required by other existing plugins
-        var danglers = underscore.difference.apply(null, diff_arr);
-        danglers && danglers.forEach(function(dangle) {
-            // TODO: fire off an uninstall for each danglin' dep
+    // if this plugin has dependencies, do a set difference to determine which dependencies are not required by other existing plugins
+    diff_arr.unshift(dependents);
+    var danglers = underscore.difference.apply(null, diff_arr);
+    if (dependents.length && danglers && danglers.length) {
+        var end = n(danglers.length, function() {
+            handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
+        });
+        danglers.forEach(function(dangler) {
+            module.exports(platform, project_dir, dangler, plugins_dir, cli_variables, www_dir, false /* TODO: should this "is_top_level" param be false for dependents? */, end);
         });
     } else {
-        // this plugin is bare, uninstall it!
+        // this plugin can get axed by itself, gogo!
         handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level, callback);
     }
 }
@@ -109,14 +112,17 @@ function handleUninstall(platform, plugin_id, plugin_et, project_dir, www_dir, p
     // run through the action stack
     action_stack.process(platform, project_dir, function(err) {
         if (err) {
-            console.error(err.message, err.stack);
-            console.error('Plugin uninstallation failed :(');
+            if (callback) callback(err);
+            else throw err;
         } else {
             // WIN!
             // queue up the plugin so prepare can remove the config changes
             config_changes.add_uninstalled_plugin_to_prepare_queue(plugins_dir, path.basename(plugin_dir), platform, is_top_level);
             // call prepare after a successful uninstall
             require('./../plugman').prepare(project_dir, platform, plugins_dir);
+            // axe the directory
+            shell.rm('-rf', plugin_dir);
+            console.log(plugin_id + ' uninstalled.');
             if (callback) callback();
         }
     });


[17/20] git commit: added dependency module tests.

Posted by fi...@apache.org.
added dependency module tests.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/617d776a
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/617d776a
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/617d776a

Branch: refs/heads/master
Commit: 617d776aaacfcbaafcacf2eaa29b8557902051d4
Parents: 62492ab
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 18:32:19 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:43 2013 -0700

----------------------------------------------------------------------
 spec/util/dependencies.spec.js |   41 +++++++++++++++++++++++++++++++++++
 src/util/dependencies.js       |    3 +-
 2 files changed, 42 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/617d776a/spec/util/dependencies.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/dependencies.spec.js b/spec/util/dependencies.spec.js
new file mode 100644
index 0000000..bbc7111
--- /dev/null
+++ b/spec/util/dependencies.spec.js
@@ -0,0 +1,41 @@
+var dependencies = require('../../src/util/dependencies'),
+    xml_helpers = require('../../src/util/xml-helpers'),
+    path = require('path'),
+    config = require('../../src/util/config-changes');
+
+describe('dependency module', function() {
+    describe('generate_dependency_info method', function() {
+        it('should return a list of top-level plugins based on what is inside a platform.json file', function() {
+            var tlps = {
+                "hello":"",
+                "isitme":"",
+                "yourelookingfor":""
+            };
+            spyOn(xml_helpers, 'parseElementtreeSync').andReturn({findall:function(){}});
+            var spy = spyOn(config, 'get_platform_json').andReturn({
+                installed_plugins:tlps,
+                dependent_plugins:[]
+            });
+            var obj = dependencies.generate_dependency_info('some dir');
+            expect(obj.top_level_plugins).toEqual(Object.keys(tlps));
+        });
+        it('should return a dependency graph for the plugins', function() {
+            var tlps = {
+                "A":"",
+                "B":""
+            };
+            var deps = {
+                "C":"",
+                "D":"",
+                "E":""
+            };
+            var spy = spyOn(config, 'get_platform_json').andReturn({
+                installed_plugins:tlps,
+                dependent_plugins:[]
+            });
+            var obj = dependencies.generate_dependency_info(path.join(__dirname, '..', 'plugins', 'dependencies'), 'android');
+            expect(obj.graph.getChain('A')).toEqual(['C','D']);
+            expect(obj.graph.getChain('B')).toEqual(['D', 'E']);
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/617d776a/src/util/dependencies.js
----------------------------------------------------------------------
diff --git a/src/util/dependencies.js b/src/util/dependencies.js
index ce77dbf..32e07eb 100644
--- a/src/util/dependencies.js
+++ b/src/util/dependencies.js
@@ -1,8 +1,7 @@
 var dep_graph = require('dep-graph'),
     path = require('path'),
     config_changes = require('./config-changes'),
-    xml_helpers = require('./xml-helpers'),
-    underscore= require('underscore');
+    xml_helpers = require('./xml-helpers');
 
 module.exports = {
     generate_dependency_info:function(plugins_dir, platform) {


[07/20] git commit: Fix tests after adding dependencies.

Posted by fi...@apache.org.
Fix tests after adding dependencies.


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/8d0e95d2
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/8d0e95d2
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/8d0e95d2

Branch: refs/heads/master
Commit: 8d0e95d22e70d9e3c58079f14b297d7c35736ce9
Parents: 67f59c9
Author: Braden Shepherdson <br...@gmail.com>
Authored: Thu May 9 14:41:44 2013 -0400
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:53:19 2013 -0700

----------------------------------------------------------------------
 src/install.js           |    3 ++-
 src/uninstall.js         |    3 ++-
 src/util/dependencies.js |    1 +
 3 files changed, 5 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/8d0e95d2/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index d7f8c46..37d789c 100644
--- a/src/install.js
+++ b/src/install.js
@@ -1,7 +1,8 @@
 var path = require('path'),
     fs   = require('fs'),
     et   = require('elementtree'),
-    config_changes = require('./util/config-changes');
+    config_changes = require('./util/config-changes'),
+    dependencies = require('./util/dependencies'),
     platform_modules = require('./platforms');
 
 // TODO: is name necessary as a param ehre?

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/8d0e95d2/src/uninstall.js
----------------------------------------------------------------------
diff --git a/src/uninstall.js b/src/uninstall.js
index c2db2e2..35838ca 100644
--- a/src/uninstall.js
+++ b/src/uninstall.js
@@ -1,7 +1,8 @@
 var path = require('path'),
     fs   = require('fs'),
     et   = require('elementtree'),
-    config_changes = require('./util/config-changes');
+    config_changes = require('./util/config-changes'),
+    platform_modules = require('./platforms');
 
 module.exports = function uninstallPlugin(platform, project_dir, name, plugins_dir, cli_variables, www_dir, callback) {
     if (!platform_modules[platform]) {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/8d0e95d2/src/util/dependencies.js
----------------------------------------------------------------------
diff --git a/src/util/dependencies.js b/src/util/dependencies.js
index 381fdf0..9f7a594 100644
--- a/src/util/dependencies.js
+++ b/src/util/dependencies.js
@@ -1,5 +1,6 @@
 var install = require('../install'),
     fetch   = require('../fetch'),
+    path    = require('path'),
     fs      = require('fs'),
     xml_helpers = require('./xml-helpers');
 


[15/20] git commit: added tests for action-stack

Posted by fi...@apache.org.
added tests for action-stack


Project: http://git-wip-us.apache.org/repos/asf/cordova-plugman/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-plugman/commit/62492ab3
Tree: http://git-wip-us.apache.org/repos/asf/cordova-plugman/tree/62492ab3
Diff: http://git-wip-us.apache.org/repos/asf/cordova-plugman/diff/62492ab3

Branch: refs/heads/master
Commit: 62492ab39d112d6b9392c3f046c7c023c55879d4
Parents: a77bfcc
Author: Fil Maj <ma...@gmail.com>
Authored: Wed May 15 18:06:09 2013 -0700
Committer: Fil Maj <ma...@gmail.com>
Committed: Thu May 16 08:55:43 2013 -0700

----------------------------------------------------------------------
 spec/util/action-stack.spec.js |   52 +++++++++++++++++++++++++++++++++++
 src/util/action-stack.js       |    2 +-
 2 files changed, 53 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/62492ab3/spec/util/action-stack.spec.js
----------------------------------------------------------------------
diff --git a/spec/util/action-stack.spec.js b/spec/util/action-stack.spec.js
new file mode 100644
index 0000000..b258a32
--- /dev/null
+++ b/spec/util/action-stack.spec.js
@@ -0,0 +1,52 @@
+var action_stack = require('../../src/util/action-stack'),
+    ios = require('../../src/platforms/ios');
+
+describe('action-stack', function() {
+    var stack;
+    beforeEach(function() {
+        stack = new action_stack();
+    });
+    describe('processing of actions', function() {
+        it('should process actions one at a time until all are done', function() {
+            var first_spy = jasmine.createSpy();
+            var first_args = [1];
+            var second_spy = jasmine.createSpy();
+            var second_args = [2];
+            var third_spy = jasmine.createSpy();
+            var third_args = [3];
+            stack.push(stack.createAction(first_spy, first_args, function(){}, []));
+            stack.push(stack.createAction(second_spy, second_args, function(){}, []));
+            stack.push(stack.createAction(third_spy, third_args, function(){}, []));
+            stack.process('android', 'blah');
+            expect(first_spy).toHaveBeenCalledWith(first_args[0]);
+            expect(second_spy).toHaveBeenCalledWith(second_args[0]);
+            expect(third_spy).toHaveBeenCalledWith(third_args[0]);
+        });
+        it('should revert processed actions if an exception occurs', function() {
+            var first_spy = jasmine.createSpy();
+            var first_args = [1];
+            var first_reverter = jasmine.createSpy();
+            var first_reverter_args = [true];
+            var process_err = 'quit peein\' on my rug, man.';
+            var second_spy = jasmine.createSpy().andCallFake(function() {
+                throw new Error(process_err);
+            });
+            var second_args = [2];
+            var third_spy = jasmine.createSpy();
+            var third_args = [3];
+            stack.push(stack.createAction(first_spy, first_args, first_reverter, first_reverter_args));
+            stack.push(stack.createAction(second_spy, second_args, function(){}, []));
+            stack.push(stack.createAction(third_spy, third_args, function(){}, []));
+            // process should throw
+            expect(function() {
+                stack.process('android', 'blah');
+            }).toThrow('Uh oh!\n' + process_err);
+            // first two actions should have been called, but not the third
+            expect(first_spy).toHaveBeenCalledWith(first_args[0]);
+            expect(second_spy).toHaveBeenCalledWith(second_args[0]);
+            expect(third_spy).not.toHaveBeenCalledWith(third_args[0]);
+            // first reverter should have been called after second action exploded
+            expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0]);
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/62492ab3/src/util/action-stack.js
----------------------------------------------------------------------
diff --git a/src/util/action-stack.js b/src/util/action-stack.js
index 115c6b7..886ff21 100644
--- a/src/util/action-stack.js
+++ b/src/util/action-stack.js
@@ -24,7 +24,7 @@ ActionStack.prototype = {
     },
     process:function(platform, project_dir, callback) {
         if (platform == 'ios') {
-            // parse xcode project file once
+            // parse xcode project files once
             var project_files = ios.parseIOSProjectFiles(project_dir);
         }
         while(this.stack.length) {