You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by an...@apache.org on 2014/04/11 21:46:31 UTC

[15/50] [abbrv] Refactoring of install & uninstall tests

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/install.js
----------------------------------------------------------------------
diff --git a/src/install.js b/src/install.js
index 0949068..1a82f65 100644
--- a/src/install.js
+++ b/src/install.js
@@ -9,7 +9,11 @@ var path = require('path'),
     Q = require('q'),
     platform_modules = require('./platforms'),
     os = require('os'),
-    isWindows = (os.platform() === 'win32');
+    underscore = require('underscore'),
+    shell   = require('shelljs'),
+    events = require('./events'),
+    plugman = require('../plugman'),
+    isWindows = (os.platform().substr(0,3) === 'win');
 
 /* INSTALL FLOW
    ------------
@@ -36,11 +40,15 @@ var path = require('path'),
 // Returns a promise.
 module.exports = function installPlugin(platform, project_dir, id, plugins_dir, options) {
     options = options || {};
+    options.is_top_level = true;
+    plugins_dir = plugins_dir || path.join(project_dir, 'cordova', 'plugins');
+
     if (!platform_modules[platform]) {
         return Q.reject(new Error(platform + " not supported."));
     }
+
     var current_stack = new action_stack();
-    options.is_top_level = true;
+
     return possiblyFetch(id, plugins_dir, options)
     .then(function(plugin_dir) {
         return runInstall(current_stack, platform, project_dir, plugin_dir, plugins_dir, options);
@@ -50,26 +58,31 @@ module.exports = function installPlugin(platform, project_dir, id, plugins_dir,
 // possible options: subdir, cli_variables, www_dir, git_ref, is_top_level
 // Returns a promise.
 function possiblyFetch(id, plugins_dir, options) {
-    var plugin_dir = path.join(plugins_dir, id);
+
+    // if plugin is a relative path, check if it already exists
+    var plugin_src_dir = path.join(plugins_dir, id);
+    if( isAbsolutePath(id) )
+        plugin_src_dir = id;
 
     // Check that the plugin has already been fetched.
-    if (fs.existsSync(plugin_dir)) {
-        return Q(plugin_dir);
+    if (fs.existsSync(plugin_src_dir)) {
+        return Q(plugin_src_dir);
     }
-    var opts = {
-        link: false,
-        subdir: options.subdir,
-        git_ref: options.git_ref,
-        client: 'plugman',
-        expected_id: options.expected_id,
-        searchpath: options.searchpath
-    };
-    return require('../plugman').raw.fetch(id, plugins_dir, opts);
+
+    var opts = underscore.extend({}, options, {
+        link: false, 
+        client: 'plugman'
+    });
+
+    // if plugin doesnt exist, use fetch to get it.
+    return plugman.raw.fetch(id, plugins_dir, opts);
 }
 
 function checkEngines(engines) {
+
     for(var i = 0; i < engines.length; i++) {
         var engine = engines[i];
+
         if(semver.satisfies(engine.currentVersion, engine.minVersion) || engine.currentVersion === null){
             // engine ok!
         }else{
@@ -88,15 +101,13 @@ function cleanVersionOutput(version, name){
         out = out.substr(0, rc_index) + '-' + out.substr(rc_index);
     }
 
-    // strip out the -dev and put a warning about using the dev branch
+    // put a warning about using the dev branch
     if (dev_index > -1) {
         // some platform still lists dev branches as just dev, set to null and continue
         if(out=="dev"){
             out = null;
-        }else{
-            out = out.substr(0, dev_index-1);
         }
-        require('../plugman').emit('verbose', name+' has been detected as using a development branch. Attemping to install anyways.');
+        events.emit('verbose', name+' has been detected as using a development branch. Attemping to install anyways.');
     }
 
     // add extra period/digits to conform to semver - some version scripts will output
@@ -124,29 +135,36 @@ function callEngineScripts(engines) {
     return Q.all(
         engines.map(function(engine){
             // CB-5192; on Windows scriptSrc doesn't have file extension so we shouldn't check whether the script exists
-            if(isWindows || fs.existsSync(engine.scriptSrc)){
 
+            var scriptPath = engine.scriptSrc ? '"' + engine.scriptSrc + '"' : null;		
+
+            if(scriptPath && (isWindows || fs.existsSync(engine.scriptSrc)) ) {
+
+                var d = Q.defer();
                 if(!isWindows) { // not required on Windows
                     fs.chmodSync(engine.scriptSrc, '755');
                 }
-                var d = Q.defer();
-                child_process.exec('"' + engine.scriptSrc + '"', function(error, stdout, stderr) {
+                child_process.exec(scriptPath, function(error, stdout, stderr) {
                     if (error) {
-                        require('../plugman').emit('log', 'Cordova project '+ engine.scriptSrc +' script failed, continuing anyways.');
+                        events.emit('warn', engine.name +' version check failed ('+ scriptPath +'), continuing anyways.');
                         engine.currentVersion = null;
-                        d.resolve(engine); // Yes, resolve. We're trying to continue despite the error.
                     } else {
-                        var version = cleanVersionOutput(stdout, engine.name);
-                        engine.currentVersion = version;
-                        d.resolve(engine);
+                        engine.currentVersion = cleanVersionOutput(stdout, engine.name);
                     }
+
+                    d.resolve(engine);
                 });
                 return d.promise;
-            }else if(engine.currentVersion){
-                return cleanVersionOutput(engine.currentVersion, engine.name)
-            }else{
-                require('../plugman').emit('verbose', 'Cordova project '+ engine.scriptSrc +' not detected (lacks a '+ engine.scriptSrc +' script), continuing.');
-                return null;
+
+            } else {
+
+                if(engine.currentVersion) {
+                    engine.currentVersion = cleanVersionOutput(engine.currentVersion, engine.name)
+                } else {
+                    events.emit('warn', engine.name +' version not detected (lacks script '+ scriptPath +' ), continuing.');
+                }
+
+                return Q(engine);
             }
         })
     );
@@ -159,6 +177,7 @@ function getEngines(pluginElement, platform, project_dir, plugin_dir){
     var uncheckedEngines = [];
     var cordovaEngineIndex, cordovaPlatformEngineIndex, theName, platformIndex, defaultPlatformIndex;
     // load in known defaults and update when necessary
+
     engines.forEach(function(engine){
         theName = engine.attrib["name"];
 
@@ -210,7 +229,7 @@ function isPluginInstalled(plugins_dir, platform, plugin_id) {
 
 // possible options: cli_variables, www_dir, is_top_level
 // Returns a promise.
-var runInstall = module.exports.runInstall = function runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options, graph) {
+var runInstall = module.exports.runInstall = function runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, options) {
     var xml_path     = path.join(plugin_dir, 'plugin.xml')
       , plugin_et    = xml_helpers.parseElementtreeSync(xml_path)
       , filtered_variables = {};
@@ -218,146 +237,228 @@ var runInstall = module.exports.runInstall = function runInstall(actions, platfo
     var plugin_id    = plugin_et.getroot().attrib['id'];
     options = options || {};
 
-    graph = graph || new dep_graph();
+    options.graph = options.graph || new dep_graph();
 
     if (isPluginInstalled(plugins_dir, platform, plugin_id)) {
         if (options.is_top_level) {
-            require('../plugman').emit('results', 'Plugin "' + plugin_id + '" already installed on ' + platform + '.');
+            events.emit('results', 'Plugin "' + plugin_id + '" already installed on ' + platform + '.');
         } else {
-            require('../plugman').emit('verbose', 'Dependent plugin "' + plugin_id + '" already installed on ' + platform + '.');
+            events.emit('verbose', 'Dependent plugin "' + plugin_id + '" already installed on ' + platform + '.');
         }
         return Q();
     }
-    require('../plugman').emit('log', 'Installing ' + plugin_id + ' (' + platform + ')');
-
-    var plugin_basename = path.basename(plugin_dir);
+    events.emit('log', 'Installing "' + plugin_id + '" for ' + platform);
 
     var theEngines = getEngines(plugin_et, platform, project_dir, plugin_dir);
+
+    var install = {
+        actions: actions,
+        platform: platform,
+        project_dir: project_dir,
+        plugins_dir: plugins_dir,
+        top_plugin_id: plugin_id,
+        top_plugin_dir: plugin_dir
+    }
+
     return callEngineScripts(theEngines)
     .then(checkEngines)
-    .then(function() {
-        // checking preferences, if certain variables are not provided, we should throw.
-        var prefs = plugin_et.findall('./preference') || [];
-        prefs = prefs.concat(plugin_et.findall('./platform[@name="'+platform+'"]/preference'));
-        var missing_vars = [];
-        prefs.forEach(function (pref) {
-            var key = pref.attrib["name"].toUpperCase();
-            options.cli_variables = options.cli_variables || {};
-            if (options.cli_variables[key] === undefined)
-                missing_vars.push(key)
-            else
-                filtered_variables[key] = options.cli_variables[key]
-        });
-        if (missing_vars.length > 0) {
-            return Q.reject(new Error('Variable(s) missing: ' + missing_vars.join(", ")));
+    .then(
+        function() {
+            // checking preferences, if certain variables are not provided, we should throw.
+            var prefs = plugin_et.findall('./preference') || [];
+            prefs = prefs.concat(plugin_et.findall('./platform[@name="'+platform+'"]/preference'));
+            var missing_vars = [];
+            prefs.forEach(function (pref) {
+                var key = pref.attrib["name"].toUpperCase();
+                options.cli_variables = options.cli_variables || {};
+                if (options.cli_variables[key] === undefined)
+                    missing_vars.push(key)
+                else
+                    filtered_variables[key] = options.cli_variables[key]
+            });
+            install.filtered_variables = filtered_variables;
+    
+            if (missing_vars.length > 0) {
+                throw new Error('Variable(s) missing: ' + missing_vars.join(", "));
+            }
+
+            // Check for dependencies
+            var dependencies = plugin_et.findall('dependency') || [];
+            dependencies = dependencies.concat(plugin_et.findall('./platform[@name="'+platform+'"]/dependency'));
+            if(dependencies && dependencies.length)
+                return installDependencies(install, dependencies, options);
+
+            return Q(true);
+        }
+    ).then(
+        function(){
+            var install_plugin_dir = path.join(plugins_dir, plugin_id);
+
+            // may need to copy to destination...
+            if ( !fs.existsSync(install_plugin_dir) ) {
+                copyPlugin(plugin_dir, plugins_dir, options.link);
+            }
+
+            return handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, install_plugin_dir, filtered_variables, options.www_dir, options.is_top_level);
+        }
+    ).fail(
+        function (error) {
+            events.emit('warn', "Failed to install '"+plugin_id+"':"+ error.stack);
+            return Q(false);
         }
+    );
+}
 
-        // Check for dependencies, (co)recurse to install each one
-        var dependencies = plugin_et.findall('dependency') || [];
-        dependencies = dependencies.concat(plugin_et.findall('./platform[@name="'+platform+'"]/dependency'));
-        if (dependencies && dependencies.length) {
-            require('../plugman').emit('verbose', 'Dependencies detected, iterating through them...');
-            var dep_plugin_id, dep_subdir, dep_git_ref;
-            return dependencies.reduce(function (soFar, dep) {
+function installDependencies(install, dependencies, options) {
+    events.emit('verbose', 'Dependencies detected, iterating through them...');
+
+    var top_plugins = path.join(install.top_plugin_dir, '..');
+
+    // Add directory of top-level plugin to search path
+    options.searchpath = options.searchpath || [];
+    if( top_plugins != install.plugins_dir && options.searchpath.indexOf(top_plugins) == -1 )
+        options.searchpath.push(top_plugins);
+
+    // Search for dependency by Id is:
+    // a) Look for {$top_plugins}/{$depId} directory
+    // b) Scan the top level plugin directory {$top_plugins} for matching id (searchpath)
+    // c) Fetch from registry
+
+    return dependencies.reduce(function(soFar, depXml) {
+        return soFar.then(
+            function() {   
+                var dep = {
+                    id: depXml.attrib.id,
+                    subdir: depXml.attrib.subdir,
+                    url: depXml.attrib.url || '',
+                    git_ref: depXml.attrib.commit
+                }
+
+                if (dep.subdir) {
+                    dep.subdir = path.join(dep.subdir.split('/'));
+                }
 
                 // We build the dependency graph only to be able to detect cycles, getChain will throw an error if it detects one
-                graph.add(plugin_id, dep.attrib.id);
-                graph.getChain(plugin_id);
-                return soFar.then(function() {
-                    dep_plugin_id = dep.attrib.id;
-                    dep_subdir = dep.attrib.subdir;
-                    var dep_url = dep.attrib.url;
-                    dep_git_ref = dep.attrib.commit;
-                    if (dep_subdir) {
-                        dep_subdir = path.join.apply(null, dep_subdir.split('/'));
+                options.graph.add(install.top_plugin_id, dep.id);
+                options.graph.getChain(install.top_plugin_id);
+
+                return tryFetchDependency(dep, install)
+                .then( 
+                    function(url){						
+                        dep.url = url;
+                        return installDependency(dep, install, options);
                     }
+                );
+            }
+        );
 
-                    // Handle relative dependency paths by expanding and resolving them.
-                    // The easy case of relative paths is to have a URL of '.' and a different subdir.
-                    // TODO: Implement the hard case of different repo URLs, rather than the special case of
-                    // same-repo-different-subdir.
-                    if (dep_url == '.') {
-                        // Look up the parent plugin's fetch metadata and determine the correct URL.
-                        var fetchdata = require('./util/metadata').get_fetch_metadata(plugin_dir);
-
-                        if (!fetchdata || !(fetchdata.source && fetchdata.source.type)) {
-                            return Q.reject(new Error('No fetch metadata found for ' + plugin_id + '. Cannot install relative dependencies.'));
-                        }
-
-                        // Now there are two cases here: local directory, and git URL.
-                        if (fetchdata.source.type === 'local') {
-                            dep_url = fetchdata.source.path;
-
-                            var d = Q.defer();
-                            child_process.exec('git rev-parse --show-toplevel', { cwd:dep_url }, function(err, stdout, stderr) {
-                                if (err) {
-                                    if (err.code == 128) {
-                                        return d.reject(new Error('Plugin ' + plugin_id + ' is not in git repository. All plugins must be in a git repository.'));
-                                    } else {
-                                        return d.reject(new Error('Failed to locate git repository for ' + plugin_id + ' plugin.'));
-                                    }
-                                }
-
-                                return d.resolve(stdout.trim());
-                            });
-                            return d.promise.then(function(git_repo) {
-                                //Clear out the subdir since the url now contains it
-                                var url = path.join(git_repo, dep_subdir);
-                                dep_subdir = "";
-                                return url;
-                            });
-                        } else if (fetchdata.source.type === 'git') {
-                            return Q(fetchdata.source.url);
-                        }
-                    } else {
-                        return Q(dep_url);
-                    }
-                })
-                .then(function(dep_url) {
-                    var dep_plugin_dir = path.join(plugins_dir, dep_plugin_id);
-                    if (fs.existsSync(dep_plugin_dir)) {
-                        require('../plugman').emit('verbose', 'Dependent plugin "' + dep_plugin_id + '" already fetched, using that version.');
-                        var opts = {
-                            cli_variables: filtered_variables,
-                            www_dir: options.www_dir,
-                            is_top_level: false
-                        };
-                        return runInstall(actions, platform, project_dir, dep_plugin_dir, plugins_dir, opts, graph);
+    }, Q(true));
+}
+
+function tryFetchDependency(dep, install) {
+
+    // Handle relative dependency paths by expanding and resolving them.
+    // The easy case of relative paths is to have a URL of '.' and a different subdir.
+    // TODO: Implement the hard case of different repo URLs, rather than the special case of
+    // same-repo-different-subdir.
+    if ( dep.url == '.' ) {
+
+        // Look up the parent plugin's fetch metadata and determine the correct URL.
+        var fetchdata = require('./util/metadata').get_fetch_metadata(install.top_plugin_dir);
+        if (!fetchdata || !(fetchdata.source && fetchdata.source.type)) {
+            throw new Error('No fetch metadata found for plugin ' + install.top_plugin_id + '. Cannot install relative dependency --> ' + dep.id);
+        }
+
+        // Now there are two cases here: local directory, and git URL.
+        var d = Q.defer();
+
+        if (fetchdata.source.type === 'local') {
+
+            dep.url = fetchdata.source.path;
+
+            child_process.exec('git rev-parse --show-toplevel', { cwd:dep.url }, function(err, stdout, stderr) {
+                if (err) {
+                    if (err.code == 128) {
+                        return d.reject(new Error('Plugin ' + dep.id + ' is not in git repository. All plugins must be in a git repository.'));
                     } else {
-                        require('../plugman').emit('verbose', 'Dependent plugin "' + dep_plugin_id + '" not fetched, retrieving then installing.');
-                        var opts = {
-                            cli_variables: filtered_variables,
-                            www_dir: options.www_dir,
-                            is_top_level: false,
-                            subdir: dep_subdir,
-                            git_ref: dep_git_ref,
-                            expected_id: dep_plugin_id,
-                            searchpath: options.searchpath
-                        };
-
-                        // CB-4770: registry fetching
-                        if(dep_url === undefined) {
-                            dep_url = dep_plugin_id;
-                        }
-
-                        return possiblyFetch(dep_url, plugins_dir, opts)
-                        .then(function(plugin_dir) {
-                            return runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, opts, graph);
-                        });
+                        return d.reject(new Error('Failed to locate git repository for ' + dep.id + ' plugin.'));
                     }
-                });
-            }, Q())
-            .then(function() {
-                return handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, options.www_dir, options.is_top_level);
+                }
+                return d.resolve(stdout.trim());
             });
-        } else {
-            return handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, options.www_dir, options.is_top_level);
+
+            return d.promise.then(function(git_repo) {
+                //Clear out the subdir since the url now contains it
+                var url = path.join(git_repo, dep.subdir);
+                dep.subdir = "";
+                return Q(url);
+            }).fail(function(error){
+//console.log("Failed to resolve url='.': " + error);
+                return Q(dep.url);	
+            });
+
+        } else if (fetchdata.source.type === 'git') {
+            return Q(fetchdata.source.url);
         }
-    });
+    }
+
+    // Test relative to parent folder 
+    if( dep.url && isRelativePath(dep.url) ) {
+        var relativePath = path.resolve(install.top_plugin_dir, '../' + dep.url);
+
+        if( fs.existsSync(relativePath) ) {
+           dep.url = relativePath;
+        }
+    }
+
+    // CB-4770: registry fetching
+    if(dep.url === undefined) {
+        dep.url = dep.plugin_id;							
+    }	
+
+    return Q(dep.url);	
 }
 
-function handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_basename, plugin_dir, filtered_variables, www_dir, is_top_level) {
-    require('../plugman').emit('verbose', 'Installing plugin ' + plugin_id);
+function installDependency(dep, install, options) {
+
+    dep.install_dir = path.join(install.plugins_dir, dep.id);
+
+    if ( fs.existsSync(dep.install_dir) ) {
+        events.emit('verbose', 'Dependent plugin "' + dep.id + '" already fetched, using that version.');
+        var opts = underscore.extend({}, options, {
+            cli_variables: install.filtered_variables, 
+            is_top_level: false
+        });
+
+       return runInstall(install.actions, install.platform, install.project_dir, dep.install_dir, install.plugins_dir, opts);
+
+    } else {
+        events.emit('verbose', 'Dependent plugin "' + dep.id + '" not fetched, retrieving then installing.');
+
+        var opts = underscore.extend({}, options, {
+            cli_variables: install.filtered_variables, 
+            is_top_level: false,
+            subdir: dep.subdir,
+            git_ref: dep.git_ref,
+            expected_id: dep.id
+        });
+
+        var dep_src = dep.url.length ? dep.url : dep.id;
+
+        return possiblyFetch(dep_src, install.plugins_dir, opts)
+        .then(
+            function(plugin_dir) {
+                return runInstall(install.actions, install.platform, install.project_dir, plugin_dir, install.plugins_dir, opts);
+            }
+        );
+    };
+}
+
+function handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plugins_dir, plugin_dir, filtered_variables, www_dir, is_top_level) {
+
+    // @tests - important this event is checked spec/install.spec.js
+    events.emit('verbose', 'Install start for "' + plugin_id + '" on ' + platform + '.');
+
     var handler = platform_modules[platform];
     www_dir = www_dir || handler.www_dir(project_dir);
 
@@ -415,20 +516,20 @@ function handleInstall(actions, plugin_id, plugin_et, platform, project_dir, plu
     return actions.process(platform, project_dir)
     .then(function(err) {
         // 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);
+        config_changes.add_installed_plugin_to_prepare_queue(plugins_dir, plugin_id, platform, filtered_variables, is_top_level);
         // call prepare after a successful install
-        require('./../plugman').prepare(project_dir, platform, plugins_dir, www_dir);
+        plugman.prepare(project_dir, platform, plugins_dir, www_dir);
 
-        require('../plugman').emit('verbose', 'Install complete for ' + plugin_id + ' on ' + platform + '.');
+        events.emit('verbose', 'Install complete for ' + plugin_id + ' on ' + platform + '.');
         // WIN!
         // Log out plugin INFO element contents in case additional install steps are necessary
         var info = plugin_et.findall('./info');
         if(info.length) {
-            require('../plugman').emit('results', interp_vars(filtered_variables, info[0].text));
+            events.emit('results', interp_vars(filtered_variables, info[0].text));
         }
         info = (platformTag ? platformTag.findall('./info') : []);
         if(info.length) {
-            require('../plugman').emit('results', interp_vars(filtered_variables, info[0].text));
+            events.emit('results', interp_vars(filtered_variables, info[0].text));
         }
     });
 }
@@ -440,3 +541,52 @@ function interp_vars(vars, text) {
     });
     return text;
 }
+
+function isAbsolutePath(path) {
+    return path && (path[0] === '/' || path[0] === '\\' || path.indexOf(':\\') > 0 );
+}
+
+function isRelativePath(path) {
+    return !isAbsolutePath();	
+}
+
+function readId(plugin_dir) {
+    var xml_path = path.join(plugin_dir, 'plugin.xml');
+    events.emit('verbose', 'Fetch is reading plugin.xml from location "' + xml_path + '"...');
+    var et = xml_helpers.parseElementtreeSync(xml_path);
+
+    return et.getroot().attrib.id;
+}
+
+// Copy or link a plugin from plugin_dir to plugins_dir/plugin_id.
+function copyPlugin(plugin_src_dir, plugins_dir, link) {
+    var plugin_id = readId(plugin_src_dir);
+    var dest = path.join(plugins_dir, plugin_id);
+    shell.rm('-rf', dest);
+
+    if (link) {
+        events.emit('verbose', 'Symlinking from location "' + plugin_src_dir + '" to location "' + dest + '"');
+        fs.symlinkSync(plugin_src_dir, dest, 'dir');
+    } else {
+        shell.mkdir('-p', dest);
+        events.emit('verbose', 'Copying from location "' + plugin_src_dir + '" to location "' + dest + '"');
+        shell.cp('-R', path.join(plugin_src_dir, '*') , dest);
+    }
+
+    return dest;
+}
+
+function isPluginInstalled(plugins_dir, platform, plugin_id) {
+    var platform_config = config_changes.get_platform_json(plugins_dir, platform);
+    for (var installed_plugin_id in platform_config.installed_plugins) {
+        if (installed_plugin_id == plugin_id) {
+            return true;
+        }
+    }
+    for (var installed_plugin_id in platform_config.dependent_plugins) {
+        if (installed_plugin_id == plugin_id) {
+            return true;
+        }
+    }
+    return false;
+}

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/platforms/android.js
----------------------------------------------------------------------
diff --git a/src/platforms/android.js b/src/platforms/android.js
index 038f92d..e396680 100644
--- a/src/platforms/android.js
+++ b/src/platforms/android.js
@@ -37,9 +37,8 @@ module.exports = {
     "source-file":{
         install:function(source_el, plugin_dir, project_dir, plugin_id) {
             var dest = path.join(source_el.attrib['target-dir'], path.basename(source_el.attrib['src']));
-            var target_path = common.resolveTargetPath(project_dir, dest);
-            if (fs.existsSync(target_path)) throw new Error('"' + target_path + '" already exists!');
-            common.copyFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
+
+            common.copyNewFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
         },
         uninstall:function(source_el, project_dir, plugin_id) {
             var dest = path.join(source_el.attrib['target-dir'], path.basename(source_el.attrib['src']));

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/platforms/blackberry10.js
----------------------------------------------------------------------
diff --git a/src/platforms/blackberry10.js b/src/platforms/blackberry10.js
index d3709c0..d9e93ad 100644
--- a/src/platforms/blackberry10.js
+++ b/src/platforms/blackberry10.js
@@ -39,9 +39,8 @@ module.exports = {
             var target = source_el.attrib['target-dir'] || plugin_id;
             TARGETS.forEach(function(arch) {
                 var dest = path.join("native", arch, "chrome", "plugin", target, path.basename(src));
-                var target_path = common.resolveTargetPath(project_dir, dest);
-                if (fs.existsSync(target_path)) throw new Error('"' + target_path + '" already exists!');
-                common.copyFile(plugin_dir, src, project_dir, dest);
+
+                common.copyNewFile(plugin_dir, src, project_dir, dest);
             });
         },
         uninstall:function(source_el, project_dir, plugin_id) {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/platforms/common.js
----------------------------------------------------------------------
diff --git a/src/platforms/common.js b/src/platforms/common.js
index 671e722..8fe1730 100644
--- a/src/platforms/common.js
+++ b/src/platforms/common.js
@@ -1,8 +1,9 @@
 var shell = require('shelljs'),
     path  = require('path'),
-    fs    = require('fs');
+    fs    = require('fs'),
+	common;
 
-module.exports = {
+module.exports = common = {
     // helper for resolving source paths from plugin.xml
     resolveSrcPath:function(plugin_dir, relative_path) {
         var full_path = path.resolve(plugin_dir, relative_path);
@@ -22,11 +23,19 @@ module.exports = {
 
         // XXX sheljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
         if(fs.statSync(src).isDirectory()) {
-            shell.cp('-R', src+'/*', dest);
+            shell.cp('-Rf', src+'/*', dest);
         } else {
             shell.cp('-f', src, dest);
         }
     },
+    // Same as copy file but throws error if target exists
+    copyNewFile:function(plugin_dir, src, project_dir, dest) {
+		var target_path = common.resolveTargetPath(project_dir, dest);
+        if (fs.existsSync(target_path)) 
+			throw new Error('"' + target_path + '" already exists!');
+
+        common.copyFile(plugin_dir, src, project_dir, dest);
+    },
     // checks if file exists and then deletes. Error if doesn't exist
     removeFile:function(project_dir, src) {
         var file = module.exports.resolveSrcPath(project_dir, src);
@@ -41,7 +50,7 @@ module.exports = {
         var file = path.resolve(project_dir, destFile);
         if (!fs.existsSync(file)) return;
 
-        module.exports.removeFileF(file);
+        common.removeFileF(file);
 
         // check if directory is empty
         var curDir = path.dirname(file);
@@ -69,7 +78,7 @@ module.exports = {
                 throw new Error('<asset> tag without required "target" attribute');
             }
 
-            module.exports.copyFile(plugin_dir, src, www_dir, target);
+            common.copyFile(plugin_dir, src, www_dir, target);
         },
         uninstall:function(asset_el, www_dir, plugin_id) {
             var target = asset_el.attrib.target || asset_el.attrib.src;
@@ -78,8 +87,8 @@ module.exports = {
                 throw new Error('<asset> tag without required "target" attribute');
             }
 
-            module.exports.removeFile(www_dir, target);
-            module.exports.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
+            common.removeFile(www_dir, target);
+            common.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
         }
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/platforms/windows8.js
----------------------------------------------------------------------
diff --git a/src/platforms/windows8.js b/src/platforms/windows8.js
index 287c048..2232192 100644
--- a/src/platforms/windows8.js
+++ b/src/platforms/windows8.js
@@ -47,9 +47,8 @@ module.exports = {
         install:function(source_el, plugin_dir, project_dir, plugin_id, project_file) {
             var targetDir = source_el.attrib['target-dir'] || '';
             var dest = path.join('www', 'plugins', plugin_id, targetDir, path.basename(source_el.attrib['src']));
-            var target_path = common.resolveTargetPath(project_dir, dest);
-            if (fs.existsSync(target_path)) throw new Error('"' + target_path + '" already exists!');
-            common.copyFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
+
+            common.copyNewFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
             // add reference to this file to jsproj.
             project_file.addSourceFile(dest);
         },

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/platforms/wp7.js
----------------------------------------------------------------------
diff --git a/src/platforms/wp7.js b/src/platforms/wp7.js
index b60697c..ebbd862 100644
--- a/src/platforms/wp7.js
+++ b/src/platforms/wp7.js
@@ -43,9 +43,8 @@ module.exports = {
     "source-file":{
         install:function(source_el, plugin_dir, project_dir, plugin_id, project_file) {
             var dest = path.join('Plugins', plugin_id, source_el.attrib['target-dir'] ? source_el.attrib['target-dir'] : '', path.basename(source_el.attrib['src']));
-            var target_path = common.resolveTargetPath(project_dir, dest);
-            if (fs.existsSync(target_path)) throw new Error('"' + target_path + '" already exists!');
-            common.copyFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
+
+            common.copyNewFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
             // add reference to this file to csproj.
             project_file.addSourceFile(dest);
         },

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/platforms/wp8.js
----------------------------------------------------------------------
diff --git a/src/platforms/wp8.js b/src/platforms/wp8.js
index 4a74a1f..8eb62c2 100644
--- a/src/platforms/wp8.js
+++ b/src/platforms/wp8.js
@@ -43,9 +43,8 @@ module.exports = {
     "source-file":{
         install:function(source_el, plugin_dir, project_dir, plugin_id, project_file) {
             var dest = path.join('Plugins', plugin_id, source_el.attrib['target-dir'] ? source_el.attrib['target-dir'] : '', path.basename(source_el.attrib['src']));
-            var target_path = common.resolveTargetPath(project_dir, dest);
-            if (fs.existsSync(target_path)) throw new Error('"' + target_path + '" already exists!');
-            common.copyFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
+
+            common.copyNewFile(plugin_dir, source_el.attrib['src'], project_dir, dest);
             // add reference to this file to csproj.
             project_file.addSourceFile(dest);
         },

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/prepare.js
----------------------------------------------------------------------
diff --git a/src/prepare.js b/src/prepare.js
index 46e9487..3fbb7c0 100644
--- a/src/prepare.js
+++ b/src/prepare.js
@@ -30,6 +30,7 @@ var platform_modules = require('./platforms'),
     fs              = require('fs'),
     shell           = require('shelljs'),
     util            = require('util'),
+    events          = require('./events'),
     plugman         = require('../plugman'),
     et              = require('elementtree');
 
@@ -47,7 +48,7 @@ module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_
     // - For each js-module (general first, then platform) build up an object storing the path and any clobbers, merges and runs for it.
     // - Write this object into www/cordova_plugins.json.
     // - Cordova.js contains code to load them at runtime from that file.
-    plugman.emit('verbose', 'Preparing ' + platform + ' project');
+    events.emit('verbose', 'Preparing ' + platform + ' project');
     var platform_json = config_changes.get_platform_json(plugins_dir, platform);
     var wwwDir = www_dir || platform_modules[platform].www_dir(project_dir);
 
@@ -60,17 +61,15 @@ module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_
             plugins_to_uninstall.forEach(function(plug) {
                 var id = plug.id;
                 var plugin_modules = path.join(plugins_www, id);
-                if (!fs.existsSync(plugin_modules)) {
-                    plugman.emit('verbose', 'There is no directory "'+plugin_modules+'"');
-                    return;
+                if (fs.existsSync(plugin_modules)) {
+                    events.emit('verbose', 'Removing plugins directory from www "'+plugin_modules+'"');
+                    shell.rm('-rf', plugin_modules);
                 }
-                plugman.emit('verbose', 'Removing plugins directory from www "'+plugin_modules+'"');
-                shell.rm('-rf', plugin_modules);
             });
         }
     }
 
-    plugman.emit('verbose', 'Processing configuration changes for plugins.');
+    events.emit('verbose', 'Processing configuration changes for plugins.');
     config_changes.process(plugins_dir, project_dir, platform);
 
     // for windows phone plaform we need to add all www resources to the .csproj file
@@ -123,7 +122,7 @@ module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_
     var plugins = Object.keys(platform_json.installed_plugins).concat(Object.keys(platform_json.dependent_plugins));
     var moduleObjects = [];
     var pluginMetadata = {};
-    plugman.emit('verbose', 'Iterating over installed plugins:', plugins);
+    events.emit('verbose', 'Iterating over installed plugins:', plugins);
 
     plugins && plugins.forEach(function(plugin) {
         var pluginDir = path.join(plugins_dir, plugin),
@@ -155,7 +154,7 @@ module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_
 
         // Copy www assets described in <asset> tags.
         assets = assets || [];
-        assets.forEach(function(asset) {
+        assets.forEach(function(asset) {					
             common.asset.install(asset, pluginDir, wwwDir);
         });
 
@@ -223,7 +222,7 @@ module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_
     final_contents += '// BOTTOM OF METADATA\n';
     final_contents += '});'; // Close cordova.define.
 
-    plugman.emit('verbose', 'Writing out cordova_plugins.js...');
+    events.emit('verbose', 'Writing out cordova_plugins.js...');
     fs.writeFileSync(path.join(wwwDir, 'cordova_plugins.js'), final_contents, 'utf-8');
 
     if(platform == 'wp7' || platform == 'wp8' || platform == "windows8") {

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/uninstall.js
----------------------------------------------------------------------
diff --git a/src/uninstall.js b/src/uninstall.js
index 0d55670..18d7a18 100644
--- a/src/uninstall.js
+++ b/src/uninstall.js
@@ -1,3 +1,4 @@
+
 var path = require('path'),
     fs   = require('fs'),
     et   = require('elementtree'),
@@ -9,85 +10,124 @@ var path = require('path'),
     underscore = require('underscore'),
     Q = require('q'),
     plugins = require('./util/plugins'),
-    platform_modules = require('./platforms');
+    underscore = require('underscore'),
+    events = require('./events'),
+    platform_modules = require('./platforms'),
+    plugman = require('../plugman');
 
 // possible options: cli_variables, www_dir
 // Returns a promise.
 module.exports = function(platform, project_dir, id, plugins_dir, options) {
+    options = options || {};
+    options.is_top_level = true;
+    plugins_dir = plugins_dir || path.join(project_dir, 'cordova', 'plugins');
+
+    // Allow path to file to grab an ID
+    var xml_path = path.join(id, 'plugin.xml');
+    if ( fs.existsSync(xml_path) ) {
+        var plugin_et  = xml_helpers.parseElementtreeSync(xml_path),
+        id = plugin_et._root.attrib['id'];
+    }
+
     return module.exports.uninstallPlatform(platform, project_dir, id, plugins_dir, options)
-    .then(function(uninstalled) {
-        return module.exports.uninstallPlugin(id, plugins_dir);
+    .then(function() {
+        return module.exports.uninstallPlugin(id, plugins_dir, options);
     });
 }
 
 // Returns a promise.
 module.exports.uninstallPlatform = function(platform, project_dir, id, plugins_dir, options) {
     options = options || {};
+    options.is_top_level = true;
+    plugins_dir = plugins_dir || path.join(project_dir, 'cordova', 'plugins');
+
     if (!platform_modules[platform]) {
         return Q.reject(new Error(platform + " not supported."));
     }
 
     var plugin_dir = path.join(plugins_dir, id);
-
     if (!fs.existsSync(plugin_dir)) {
         return Q.reject(new Error('Plugin "' + id + '" not found. Already uninstalled?'));
     }
 
     var current_stack = new action_stack();
 
-    options.is_top_level = true;
-    return runUninstall(current_stack, platform, project_dir, plugin_dir, plugins_dir, options);
+    return runUninstallPlatform(current_stack, platform, project_dir, plugin_dir, plugins_dir, options);
 };
 
 // Returns a promise.
-module.exports.uninstallPlugin = function(id, plugins_dir) {
+module.exports.uninstallPlugin = function(id, plugins_dir, options) {
+    options = options || {};
+
     var plugin_dir = path.join(plugins_dir, id);
+
     // If already removed, skip.
     if (!fs.existsSync(plugin_dir)) {
         return Q();
     }
-    var xml_path     = path.join(plugin_dir, 'plugin.xml')
-      , plugin_et    = xml_helpers.parseElementtreeSync(xml_path);
 
-    require('../plugman').emit('log', 'Deleting plugin ' + id);
+    var xml_path  = path.join(plugin_dir, 'plugin.xml')
+      , plugin_et = xml_helpers.parseElementtreeSync(xml_path);
+
+    events.emit('log', 'Deleting "'+ id +'"');
 
     var doDelete = function(id) {
         var plugin_dir = path.join(plugins_dir, id);
-        if (!fs.existsSync(plugin_dir)) return;
+        if ( !fs.existsSync(plugin_dir) ) 
+            return;
+
         shell.rm('-rf', plugin_dir);
-        require('../plugman').emit('verbose', id + ' deleted.');
+        events.emit('verbose', '"'+ id +'" deleted.');
     };
 
     // We've now lost the metadata for the plugins that have been uninstalled, so we can't use that info.
     // Instead, we list all dependencies of the target plugin, and check the remaining metadata to see if
     // anything depends on them, or if they're listed as top-level.
     // If neither, they can be deleted.
+    var top_plugin_id = id;
     var toDelete = plugin_et.findall('dependency');
     toDelete = toDelete && toDelete.length ? toDelete.map(function(p) { return p.attrib.id; }) : [];
-    toDelete.push(id);
+    toDelete.push(top_plugin_id);
 
     // Okay, now we check if any of these are depended on, or top-level.
     // Find the installed platforms by whether they have a metadata file.
-    var platforms = Object.keys(platform_modules).filter(function(plat) {
-        return fs.existsSync(path.join(plugins_dir, plat + '.json'));
+    var platforms = Object.keys(platform_modules).filter(function(platform) {
+        return fs.existsSync(path.join(plugins_dir, platform + '.json'));
     });
 
-    var found = [];
-    platforms.forEach(function(plat) {
-        var tlps = dependencies.generate_dependency_info(plugins_dir, plat).top_level_plugins;
+    var dependList = {};
+    platforms.forEach(function(platform) {				   
+        var depsInfo = dependencies.generate_dependency_info(plugins_dir, platform);
+        var tlps = depsInfo.top_level_plugins,
+            deps, i;
+
         toDelete.forEach(function(plugin) {
-            if (tlps.indexOf(plugin) >= 0 || dependencies.dependents(plugin, plugins_dir, plat).length) {
-                found.push(plugin);
+            deps = dependencies.dependents(plugin, depsInfo);
+            var i = deps.indexOf(top_plugin_id);
+            if(i >= 0)
+                 deps.splice(i, 1); // remove current/top-level plugin as blocking uninstall
+
+            if(deps.length) {
+                dependList[plugin] = deps.join(', ');
             }
         });
     });
 
-    var danglers = underscore.difference(toDelete, found);
-    if (danglers && danglers.length) {
-        require('../plugman').emit('log', 'Found ' + danglers.length + ' removable plugins. Deleting them.');
-        danglers.forEach(doDelete);
-    } else {
-        require('../plugman').emit('log', 'No dangling plugins to remove.');
+    var i, plugin_id, msg;
+    for(i in toDelete) {
+        plugin_id = toDelete[i];
+
+        if( dependList[plugin_id] ) {
+            msg = '"' + plugin_id + '" is required by ('+ dependList[plugin_id] + ')';
+            if(options.force) {
+                events.emit('log', msg +' but forcing removal.');
+            } else {
+                events.emit('warn', msg +' and cannot be removed (hint: use -f or --force)');
+                continue;
+            }
+        }
+
+        doDelete(plugin_id);
     }
 
     return Q();
@@ -95,36 +135,47 @@ module.exports.uninstallPlugin = function(id, plugins_dir) {
 
 // possible options: cli_variables, www_dir, is_top_level
 // Returns a promise
-function runUninstall(actions, platform, project_dir, plugin_dir, plugins_dir, options) {
-    var xml_path     = path.join(plugin_dir, 'plugin.xml')
-      , plugin_et    = xml_helpers.parseElementtreeSync(xml_path);
-    var plugin_id    = plugin_et._root.attrib['id'];
+function runUninstallPlatform(actions, platform, project_dir, plugin_dir, plugins_dir, options) {
     options = options || {};
 
+    var xml_path     = path.join(plugin_dir, 'plugin.xml');
+    var plugin_et    = xml_helpers.parseElementtreeSync(xml_path);
+    var plugin_id    = plugin_et._root.attrib['id'];
+
+    // deps info can be passed recusively
+    var depsInfo = options.depsInfo || dependencies.generate_dependency_info(plugins_dir, platform, 'remove');
+
     // Check that this plugin has no dependents.
-    var dependents = dependencies.dependents(plugin_id, plugins_dir, platform);
+    var dependents = dependencies.dependents(plugin_id, depsInfo, platform);
+
     if(options.is_top_level && dependents && dependents.length > 0) {
-        require('../plugman').emit('verbose', 'Other top-level plugins (' + dependents.join(', ') + ') depend on ' + plugin_id + ', skipping uninstallation.');
-        return Q();
+        var msg = "The plugin '"+ plugin_id +"' is required by (" + dependents.join(', ') + ")";
+        if(options.force) {
+            events.emit("info", msg + " but forcing removal");	
+        } else {
+            return Q.reject( new Error(msg + ", skipping uninstallation.") );
+        }
     }
 
     // Check how many dangling dependencies this plugin has.
-    var dependency_info = dependencies.generate_dependency_info(plugins_dir, platform);
-    var deps = dependency_info.graph.getChain(plugin_id);
-    var danglers = dependencies.danglers(plugin_id, plugins_dir, platform);
+    var deps = depsInfo.graph.getChain(plugin_id);
+    var danglers = dependencies.danglers(plugin_id, depsInfo, platform);
 
     var promise;
     if (deps && deps.length && danglers && danglers.length) {
-        require('../plugman').emit('log', 'Uninstalling ' + danglers.length + ' dangling dependent plugins.');
+        
+        // @tests - important this event is checked spec/uninstall.spec.js
+        events.emit('log', 'Uninstalling ' + danglers.length + ' dependent plugins.');
         promise = Q.all(
             danglers.map(function(dangler) {
-                var dependent_path = path.join(plugins_dir, dangler);
-                var opts = {
-                    www_dir: options.www_dir,
-                    cli_variables: options.cli_variables,
-                    is_top_level: dependency_info.top_level_plugins.indexOf(dangler) > -1
-                };
-                return runUninstall(actions, platform, project_dir, dependent_path, plugins_dir, opts);
+                var dependent_path = dependencies.resolvePath(dangler, plugins_dir);
+
+                var opts = underscore.extend({}, options, { 
+                    is_top_level: depsInfo.top_level_plugins.indexOf(dangler) > -1,
+                    depsInfo: depsInfo
+                });
+
+                return runUninstallPlatform(actions, platform, project_dir, dependent_path, plugins_dir, opts);
             })
         );
     } else {
@@ -142,7 +193,7 @@ function handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, w
     var handler = platform_modules[platform];
     var platformTag = plugin_et.find('./platform[@name="'+platform+'"]');
     www_dir = www_dir || handler.www_dir(project_dir);
-    require('../plugman').emit('log', 'Uninstalling ' + plugin_id + ' from ' + platform);
+    events.emit('log', 'Uninstalling ' + plugin_id + ' from ' + platform);
     
     var assets = plugin_et.findall('./asset');
     if (platformTag) {
@@ -201,11 +252,10 @@ function handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, w
     return actions.process(platform, project_dir)
     .then(function() {
         // WIN!
-        require('../plugman').emit('verbose', plugin_id + ' uninstalled from ' + platform + '.');
+        events.emit('verbose', plugin_id + ' uninstalled from ' + platform + '.');
         // 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);
+        config_changes.add_uninstalled_plugin_to_prepare_queue(plugins_dir, plugin_id, platform, is_top_level);
         // call prepare after a successful uninstall
-        require('./../plugman').prepare(project_dir, platform, plugins_dir, www_dir);
+        plugman.prepare(project_dir, platform, plugins_dir, www_dir);
     });
 }
-

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/util/default-engines.js
----------------------------------------------------------------------
diff --git a/src/util/default-engines.js b/src/util/default-engines.js
index 2ca8b13..926c206 100644
--- a/src/util/default-engines.js
+++ b/src/util/default-engines.js
@@ -3,10 +3,9 @@ var path = require('path');
 module.exports = function(project_dir){
     return {
         'cordova': 
-            { 'platform':'*', 'scriptSrc': path.join(project_dir,'cordova','version') },   
-        // no location needed for plugman as this should be the calling process
+            { 'platform':'*', 'scriptSrc': path.join(project_dir,'cordova','version') },
         'cordova-plugman': 
-            { 'platform':'*', 'currentVersion': process.version },
+            { 'platform':'*', 'currentVersion': require('../../package.json').version },
         'cordova-android': 
             { 'platform':'android', 'scriptSrc': path.join(project_dir,'cordova','version') },
         'cordova-ios': 

http://git-wip-us.apache.org/repos/asf/cordova-plugman/blob/ae2ce7ac/src/util/dependencies.js
----------------------------------------------------------------------
diff --git a/src/util/dependencies.js b/src/util/dependencies.js
index acd7816..8abbc5b 100644
--- a/src/util/dependencies.js
+++ b/src/util/dependencies.js
@@ -4,33 +4,43 @@ var dep_graph = require('dep-graph'),
     plugman = require('../../plugman'),
     config_changes = require('./config-changes'),
     underscore = require('underscore'),
-    xml_helpers = require('./xml-helpers');
+    xml_helpers = require('./xml-helpers'),
+    package;
+
+module.exports = package = {
+
+    resolvePath: function(plugin_id, plugins_dir)
+    {
+        return path.join(plugins_dir, plugin_id);
+    },
+
+    resolveConfig: function(plugin_id, plugins_dir)
+    {
+        return path.join(plugins_dir, plugin_id, 'plugin.xml');
+    },
 
-module.exports = {
     generate_dependency_info:function(plugins_dir, platform) {
         var json = config_changes.get_platform_json(plugins_dir, platform);
+
+        // TODO: store whole dependency tree in plugins/[platform].json
+        // in case plugins are forcefully removed...
         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'));
+        Object.keys(json.installed_plugins).forEach(function(plugin_id) {
+            tlps.push(plugin_id);
+
+            var xml = xml_helpers.parseElementtreeSync( package.resolveConfig(plugin_id, plugins_dir) );
             var deps = xml.findall('dependency');
+
             deps && deps.forEach(function(dep) {
-                var id = dep.attrib.id;
-                graph.add(tlp, id);
+                graph.add(plugin_id, dep.attrib.id);
             });
         });
-        Object.keys(json.dependent_plugins).forEach(function(plug) {
-            var pluginXML = path.join(plugins_dir, plug, 'plugin.xml');
-            if (!fs.existsSync(pluginXML)) {
-                plugman.emit('verbose', 'Could not find "' + pluginXML + '"');
-                return;
-            }
-            var xml = xml_helpers.parseElementtreeSync(pluginXML);
+        Object.keys(json.dependent_plugins).forEach(function(plugin_id) {
+            var xml = xml_helpers.parseElementtreeSync( package.resolveConfig(plugin_id, plugins_dir) );
             var deps = xml.findall('dependency');
             deps && deps.forEach(function(dep) {
-                var id = dep.attrib.id;
-                graph.add(plug, id);
+                graph.add(plugin_id, dep.attrib.id);
             });
         });
 
@@ -42,10 +52,13 @@ module.exports = {
 
     // Returns a list of top-level plugins which are (transitively) dependent on the given plugin.
     dependents: function(plugin_id, plugins_dir, platform) {
-        var dependency_info = module.exports.generate_dependency_info(plugins_dir, platform);
-        var graph = dependency_info.graph;
+        if(typeof plugins_dir == 'object')
+            var depsInfo = plugins_dir;
+        else
+            var depsInfo = package.generate_dependency_info(plugins_dir, platform);
 
-        var tlps = dependency_info.top_level_plugins;
+        var graph = depsInfo.graph;
+        var tlps = depsInfo.top_level_plugins;
         var dependents = tlps.filter(function(tlp) {
             return tlp != plugin_id && graph.getChain(tlp).indexOf(plugin_id) >= 0;
         });
@@ -56,11 +69,15 @@ module.exports = {
     // Returns a list of plugins which the given plugin depends on, for which it is the only dependent.
     // In other words, if the given plugin were deleted, these dangling dependencies should be deleted too.
     danglers: function(plugin_id, plugins_dir, platform) {
-        var dependency_info = module.exports.generate_dependency_info(plugins_dir, platform);
-        var graph = dependency_info.graph;
+        if(typeof plugins_dir == 'object')
+            var depsInfo = plugins_dir;
+        else
+            var depsInfo = package.generate_dependency_info(plugins_dir, platform);
+
+        var graph = depsInfo.graph;
         var dependencies = graph.getChain(plugin_id);
 
-        var tlps = dependency_info.top_level_plugins;
+        var tlps = depsInfo.top_level_plugins;
         var diff_arr = [];
         tlps.forEach(function(tlp) {
             if (tlp != plugin_id) {
@@ -76,4 +93,4 @@ module.exports = {
         danglers = danglers && danglers.filter(function(x) { return tlps.indexOf(x) < 0; });
         return danglers;
     }
-};
+};
\ No newline at end of file