blob: 9bdc35988bda5de5e0fcd43ad7f7d5558aa3e486 [file] [log] [blame]
var path = require('path'),
fs = require('fs'),
et = require('elementtree'),
shell= require('shelljs'),
config_changes = require('./util/config-changes'),
xml_helpers = require('./util/xml-helpers'),
action_stack = require('./util/action-stack'),
dependencies = require('./util/dependencies'),
underscore = require('underscore'),
Q = require('q'),
plugins = require('./util/plugins'),
platform_modules = require('./platforms');
// possible options: cli_variables, www_dir
// Returns a promise.
module.exports = function(platform, project_dir, id, plugins_dir, options) {
return module.exports.uninstallPlatform(platform, project_dir, id, plugins_dir, options)
.then(function(uninstalled) {
return module.exports.uninstallPlugin(id, plugins_dir);
});
}
// Returns a promise.
module.exports.uninstallPlatform = function(platform, project_dir, id, plugins_dir, options) {
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);
};
// Returns a promise.
module.exports.uninstallPlugin = function(id, plugins_dir) {
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 doDelete = function(id) {
var plugin_dir = path.join(plugins_dir, id);
if (!fs.existsSync(plugin_dir)) return;
shell.rm('-rf', plugin_dir);
require('../plugman').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 toDelete = plugin_et.findall('dependency');
toDelete = toDelete && toDelete.length ? toDelete.map(function(p) { return p.attrib.id; }) : [];
toDelete.push(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 found = [];
platforms.forEach(function(plat) {
var tlps = dependencies.generate_dependency_info(plugins_dir, plat).top_level_plugins;
toDelete.forEach(function(plugin) {
if (tlps.indexOf(plugin) >= 0 || dependencies.dependents(plugin, plugins_dir, plat).length) {
found.push(plugin);
}
});
});
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.');
}
return Q();
};
// 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'];
options = options || {};
// Check that this plugin has no dependents.
var dependents = dependencies.dependents(plugin_id, plugins_dir, 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();
}
// 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 promise;
if (deps && deps.length && danglers && danglers.length) {
require('../plugman').emit('log', 'Uninstalling ' + danglers.length + ' dangling 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);
})
);
} else {
promise = Q();
}
return promise.then(function() {
return handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, options.www_dir, plugins_dir, plugin_dir, options.is_top_level);
});
}
// Returns a promise.
function handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, www_dir, plugins_dir, plugin_dir, is_top_level) {
var platform_modules = require('./platforms');
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);
var assets = plugin_et.findall('./asset');
if (platformTag) {
var sourceFiles = platformTag.findall('./source-file'),
headerFiles = platformTag.findall('./header-file'),
libFiles = platformTag.findall('./lib-file'),
resourceFiles = platformTag.findall('./resource-file');
frameworkFiles = platformTag.findall('./framework[@custom="true"]');
assets = assets.concat(platformTag.findall('./asset'));
// queue up native stuff
sourceFiles && sourceFiles.forEach(function(source) {
actions.push(actions.createAction(handler["source-file"].uninstall, [source, project_dir, plugin_id], handler["source-file"].install, [source, plugin_dir, project_dir, plugin_id]));
});
headerFiles && headerFiles.forEach(function(header) {
actions.push(actions.createAction(handler["header-file"].uninstall, [header, project_dir, plugin_id], handler["header-file"].install, [header, plugin_dir, project_dir, plugin_id]));
});
resourceFiles && resourceFiles.forEach(function(resource) {
actions.push(actions.createAction(handler["resource-file"].uninstall, [resource, project_dir], handler["resource-file"].install, [resource, plugin_dir, project_dir]));
});
// CB-5238 custom frameworks only
frameworkFiles && frameworkFiles.forEach(function(framework) {
actions.push(actions.createAction(handler["framework"].uninstall, [framework, project_dir, plugin_id], handler["framework"].install, [framework, plugin_dir, project_dir]));
});
libFiles && libFiles.forEach(function(source) {
actions.push(actions.createAction(handler["lib-file"].uninstall, [source, project_dir, plugin_id], handler["lib-file"].install, [source, plugin_dir, project_dir, plugin_id]));
});
}
// queue up asset installation
var common = require('./platforms/common');
assets && assets.forEach(function(asset) {
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
return actions.process(platform, project_dir)
.then(function() {
// WIN!
require('../plugman').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);
// call prepare after a successful uninstall
require('./../plugman').prepare(project_dir, platform, plugins_dir, www_dir);
});
}