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
