From 98cf0ae6e930bb0459a272af3fa008cbdf48ff1e Mon Sep 17 00:00:00 2001 From: Matthew Arbesfeld Date: Thu, 7 Aug 2014 13:10:50 -0700 Subject: [PATCH] Wip refactoring --- tools/commands-cordova.js | 227 ++++++++++++++++++--------------- tools/commands-packages.js | 15 +++ tools/commands.js | 3 +- tools/tests/cordova-plugins.js | 28 ++-- 4 files changed, 154 insertions(+), 119 deletions(-) diff --git a/tools/commands-cordova.js b/tools/commands-cordova.js index 40d8c216e9..062e728955 100644 --- a/tools/commands-cordova.js +++ b/tools/commands-cordova.js @@ -24,6 +24,8 @@ var execFileSync = require('./utils.js').execFileSync; var cordova = exports; +var supportedPlatforms = ['ios', 'android', 'firefoxos']; + var getLoadedPackages = _.once(function () { var uniload = require('./uniload.js'); return uniload.load({ @@ -81,35 +83,143 @@ var fetchCordovaPluginFromShaUrl = return pluginPath; }; -var ensureCordovaPlatforms = function (platforms, cordovaPath) { +var checkIsValidPlugin = function (name) { + var pluginName = name.split('@')[0]; + var pluginHash = { + pluginName: name.split('@')[1] + }; + + // check that every plugin is specifying either an exact constraint or a + // tarball url with sha + utils.ensureOnlyExactVersions(pluginHash); +}; + +//////////////////////////////////////////////////////////////////////////////// +// ensureCordova +//////////////////////////////////////////////////////////////////////////////// + +// Creates a Cordova project if necessary. Then synchronizes app-level plugins +// and platofrms. +cordova.ensureCordovaProject = function (cordovaPath, appName) { + var localPluginsPath = path.join(cordovaPath, 'local-plugins'); + if (! fs.existsSync(cordovaPath)) { + try { + execFileSync('cordova', ['create', path.basename(cordovaPath), + 'com.meteor.' + appName, + appName.replace(/\s/g, '')], + { cwd: path.dirname(cordovaPath) }); + + // create a folder for storing local plugins + // XXX cache them there + files.mkdir_p(localPluginsPath); + } catch (err) { + process.stderr.write("Error creating Cordova project: " + + err.message + "\n"); + } + } + + cordova.ensureCordovaPlatforms(cordovaPath); + cordova.ensureCordovaPlugins(cordovaPath); +}; + +// Ensures that the Cordova platforms are synchronized with the app-level +// platforms. +cordova.ensureCordovaPlatforms = function (cordovaPath) { + var platforms = project.getCordovaPlatforms(); var platformsOutput = execFileSync('cordova', ['platform', 'list'], { cwd: cordovaPath }).stdout; var installedPlatforms = _.map(platformsOutput.split('\n')[0].match(/Installed platforms: (.*)/)[1].split(', '), function (s) { return s.split(' ')[0]; }); _.each(platforms, function (platform) { - if (! _.contains(installedPlatforms, platform)) + if (! _.contains(installedPlatforms, platform) && + _.contains(supportedPlatforms, platform)) execFileSync('cordova', ['platform', 'add', platform], { cwd: cordovaPath }); }); _.each(installedPlatforms, function (platform) { - if (! _.contains(platforms, platform)) + if (! _.contains(platforms, platform) && + _.contains(supportedPlatforms, platform)) execFileSync('cordova', ['platform', 'rm', platform], { cwd: cordovaPath }); }); return true; }; -// Creates a Cordova project if necessary and makes sure added Cordova -// platforms and Cordova plugins are up to date with the project's -// definition. -cordova.ensureCordovaProject = function (options, cordovaPath, bundlePath) { - if (! options.platforms) { - throw new Error("Must pass platforms to a Cordova project."); +// Ensures that the Cordova plugins are synchronized with the app-level +// plugins. +cordova.ensureCordovaPlugins = function (cordovaPath, appPlugins) { + appPlugins = appPlugins || {}; + + // XXX compare the latest used sha's with the currently required sha's for + // plugins fetched from a github/tarball url. + var pluginsOutput = execFileSync('cordova', ['plugin', 'list'], + { cwd: cordovaPath }).stdout; + + var installedPlugins = {}; + // Check if there are any plugins + if (! pluginsOutput.match(/No plugins added/)) { + _.each(pluginsOutput.split('\n'), function (line) { + line = line.trim(); + if (line === '') + return; + var plugin = line.split(' ')[0]; + var version = line.split(' ')[1]; + installedPlugins[plugin] = version; + }); } - var bundler = require(path.join(__dirname, 'bundler.js')); + // XXX the project-level cordova plugins deps override the package-level ones + _.extend(appPlugins, project.getCordovaPlugins()); + _.each(appPlugins, function (version, name) { + // no-op if this plugin is already installed + if (_.has(installedPlugins, name) + && installedPlugins[name] === version + && _.isEqual(oldSettings[name], newSettings[name])) + return; + + if (_.has(installedPlugins, name)) + execFileSync('cordova', ['plugin', 'rm', name], { cwd: cordovaPath }); + + // XXX do something different for plugins fetched from a url. + var pluginInstallCommand = version ? name + '@' + version : name; + + if (version && utils.isUrlWithSha(version)) { + pluginInstallCommand = + fetchCordovaPluginFromShaUrl(version, localPluginsPath, name); + } + + var additionalArgs = []; + // XXX how do we get settings to work now? Do we require settings to be + // passed every time we add a plugin? + // if (newSettings[name]) { + // if (! _.isObject(newSettings[name])) + // throw new Error("Meteor.settings.cordova." + name + " is expected to be an object"); + // _.each(newSettings[name], function (value, variable) { + // additionalArgs.push("--variable"); + // additionalArgs.push(variable + "=" + JSON.stringify(value)); + // }); + // } + + var execRes = execFileSync('cordova', + ['plugin', 'add', pluginInstallCommand].concat(additionalArgs), { cwd: cordovaPath }); + if (! execRes.success) + throw new Error("Failed to install plugin " + name + ": " + execRes.stderr); + }); + + _.each(installedPlugins, function (version, name) { + if (! _.has(appPlugins, name)) + execFileSync('cordova', ['plugin', 'rm', name], { cwd: cordovaPath }); + }); +}; + +// Build a Cordova project, creating a Cordova project if necessary. +cordova.buildCordova = function (cordovaPath, options) { + cordova.ensureCordovaProject(cordovaPath, options.appName); + + var bundler = require(path.join(__dirname, 'bundler.js')); + var newSettings = options.settings || {}; var webArchName = 'web.cordova'; var bundleResult = bundler.bundle({ @@ -127,28 +237,6 @@ cordova.ensureCordovaProject = function (options, cordovaPath, bundlePath) { } var programPath = path.join(bundlePath, 'programs'); - var localPluginsPath = path.join(cordovaPath, 'local-plugins'); - var newSettings = options.settings || {}; - - if (! fs.existsSync(cordovaPath)) { - execFileSync('cordova', ['create', path.basename(cordovaPath), - 'com.meteor.' + options.appName, - options.appName.replace(/\s/g, '')], - { cwd: path.dirname(cordovaPath) }); - - // XXX a hack as platforms management is not implemented yet - var platforms = options.platforms || "firefoxos"; - _.each(platforms, function (platform) { - execFileSync('cordova', ['platforms', 'add', platform], - { cwd: cordovaPath }); - }); - - - // create a folder for storing local plugins - // XXX cache them there - files.mkdir_p(localPluginsPath); - } - var oldSettings = {}; try { fs.readFileSync(path.join(cordovaPath, 'cordova-settings.json'), "utf8"); @@ -183,77 +271,12 @@ cordova.ensureCordovaProject = function (options, cordovaPath, bundlePath) { var loaderCode = fs.readFileSync(loaderPath); fs.writeFileSync(path.join(wwwPath, 'meteor_cordova_loader.js'), loaderCode); - ensureCordovaPlatforms(options.platforms, cordovaPath); - - // Compare the state of plugins in the Cordova project and the required by the - // Meteor project. - // XXX compare the latest used sha's with the currently required sha's for - // plugins fetched from a github/tarball url. - var pluginsOutput = execFileSync('cordova', ['plugin', 'list'], - { cwd: cordovaPath }).stdout; - - var installedPlugins = {}; - // Check if there are any plugins - if (! pluginsOutput.match(/No plugins added/)) { - _.each(pluginsOutput.split('\n'), function (line) { - line = line.trim(); - if (line === '') - return; - var plugin = line.split(' ')[0]; - var version = line.split(' ')[1]; - installedPlugins[plugin] = version; - }); - } - - var requiredPlugins = bundleResult.starManifest.cordovaDependencies; - // XXX the project-level cordova plugins deps override the package-level ones - _.extend(requiredPlugins, options.plugins || {}); - - _.each(requiredPlugins, function (version, name) { - // no-op if this plugin is already installed - if (_.has(installedPlugins, name) - && installedPlugins[name] === version - && _.isEqual(oldSettings[name], newSettings[name])) - return; - - if (_.has(installedPlugins, name)) - execFileSync('cordova', ['plugin', 'rm', name], { cwd: cordovaPath }); - - // XXX do something different for plugins fetched from a url. - var pluginInstallCommand = version ? name + '@' + version : name; - - if (version && utils.isUrlWithSha(version)) { - pluginInstallCommand = - fetchCordovaPluginFromShaUrl(version, localPluginsPath, name); - } - - var additionalArgs = []; - if (newSettings[name]) { - if (! _.isObject(newSettings[name])) - throw new Error("Meteor.settings.cordova." + name + " is expected to be an object"); - _.each(newSettings[name], function (value, variable) { - additionalArgs.push("--variable"); - additionalArgs.push(variable + "=" + JSON.stringify(value)); - }); - } - - var execRes = execFileSync('cordova', - ['plugin', 'add', pluginInstallCommand].concat(additionalArgs), { cwd: cordovaPath }); - if (! execRes.success) - throw new Error("Failed to install plugin " + name + ": " + execRes.stderr); - }); - - _.each(installedPlugins, function (version, name) { - if (! _.has(requiredPlugins, name)) - execFileSync('cordova', ['plugin', 'rm', name], { cwd: cordovaPath }); - }); - execFileSync('cordova', ['build'], { cwd: cordovaPath }); }; // Start the simulator or physical device for a specific platform. // platformName is of the form ios/ios-device/android/android-device -cordova.execCordovaOnPlatform = function (platformName, cordovaPath, options) { +cordova.execCordovaOnPlatform = function (cordovaPath, platformName, options) { // XXX error if an invalid platform var platform = platformName.split('-')[0]; var isDevice = platformName.split('-')[1] === 'device'; @@ -275,7 +298,6 @@ cordova.execCordovaOnPlatform = function (platformName, cordovaPath, options) { // packages - list of strings cordova.filterPackages = function (packages) { - var supportedPlatforms = ['ios', 'android', 'firefoxos']; // We hard-code the 'cordova' and 'platform' namespaces var ret = { rest: [], @@ -286,9 +308,10 @@ cordova.filterPackages = function (packages) { _.each(packages, function (p) { var namespace = p.split(':')[0]; var name = p.split(':')[1]; - if (namespace === 'cordova') + if (namespace === 'cordova') { + checkIsValidPlugin(name); ret.plugins.push(name); - else if (namespace === 'platform') { + } else if (namespace === 'platform') { if (! _.contains(supportedPlatforms, name)) throw new Error(name + ": no such platform"); ret.platforms.push(name); @@ -353,8 +376,6 @@ main.registerCommand({ var projectOptions = _.pick(options, 'port', 'host'); projectOptions.appName = appName; projectOptions.cordovaSettings = cordovaSettings; - projectOptions.plugins = project.getCordovaPlugins(); - projectOptions.platforms = project.getCordovaPlatforms(); // XXX in Android simulators you can't access localhost and the correct way is // to use "10.0.2.2" instead. diff --git a/tools/commands-packages.js b/tools/commands-packages.js index 4fdf68fa80..473b574ec0 100644 --- a/tools/commands-packages.js +++ b/tools/commands-packages.js @@ -1394,6 +1394,21 @@ main.registerCommand({ }); project.addCordovaPlugins(pluginsDict); + if (cordovaPlugins.length || cordovaPlatforms.length) { + var cordovaPath = + path.join(options.appDir, '.meteor', 'local', 'cordova-build'); + var appName = path.basename(options.appDir); + cordova.ensureCordovaProject(cordovaPath, appName); + } + + _.each(cordovaPlatforms, function (platform) { + process.stdout.write("added platform " + platform + "\n"); + }); + + _.each(cordovaPlugins, function (plugin) { + process.stdout.write("added cordova plugin " + plugin + "\n"); + }); + var args = filteredPackages.rest; if (_.isEmpty(args)) diff --git a/tools/commands.js b/tools/commands.js index 30f42682cb..8f3a75ef3a 100644 --- a/tools/commands.js +++ b/tools/commands.js @@ -280,7 +280,7 @@ main.registerCommand({ var cordovaPlatforms = project.getCordovaPlatforms(); _.each(requestedPlatforms, function (platform) { if (! _.contains(cordovaPlatforms, platform)) - throw new Error("XXX Requested platform " + platform + " which is not added to the project. Enter `meteor add platforms:" + platform + "` to add it."); + throw new Error("XXX Requested platform " + platform + " which is not added to the project. Enter `meteor add platform:" + platform + "` to add it."); }); var cordovaSettings = null; @@ -290,7 +290,6 @@ main.registerCommand({ } var cordovaOptions = { - platforms: requestedPlatforms, host: proxyHost, port: proxyPort, appName: path.basename(options.appDir), diff --git a/tools/tests/cordova-plugins.js b/tools/tests/cordova-plugins.js index 1368437f3c..b969f950aa 100644 --- a/tools/tests/cordova-plugins.js +++ b/tools/tests/cordova-plugins.js @@ -84,7 +84,7 @@ var checkUserPlugins = function(sand, plugins) { // Add plugins to an app. Change the contents of the plugins and their // dependencies, make sure that the app still refreshes. -selftest.define("change plugins", function () { +selftest.define("change cordova plugins", function () { var s = new Sandbox(); var run; @@ -121,7 +121,7 @@ selftest.define("change plugins", function () { // Add plugins through the command line, and make sure that the correct set of // changes is reflected in .meteor/packages, .meteor/versions and list -selftest.define("add plugins", function () { +selftest.define("add cordova plugins", function () { var s = new Sandbox(); var run; @@ -131,17 +131,20 @@ selftest.define("add plugins", function () { s.set("METEOR_TEST_TMP", files.mkdtemp()); s.set("METEOR_OFFLINE_CATALOG", "t"); - run = s.run("cordova", "create", "firefoxos"); - run.waitSecs(10); - run.expectExit(0); - run = s.run("remove", "standard-app-packages"); run.match("removed"); - run = s.run("cordova", "plugin", "add", "org.apache.cordova.camera@0.3.0"); - run.match("Added org.apache.cordova.camera"); + run = s.run("run", "firefoxos"); + run.matchErr("not added to the project"); + run.matchErr("meteor add platform:"); - run = s.run("cordova", "plugin", "add", "org.apache.cordova.file"); + run = s.run("add", "platform:firefoxos"); + run.match("added platform"); + + run = s.run("add", "cordova:org.apache.cordova.camera@0.3.0"); + run.match("added cordova plugin org.apache.cordova.camera"); + + run = s.run("add", "cordova:org.apache.cordova.file"); run.matchErr("Must declare exact version"); checkUserPlugins(s, ["org.apache.cordova.camera"]); @@ -152,20 +155,17 @@ selftest.define("add plugins", function () { // XXX message about a plugin? checkUserPlugins(s, ["org.apache.cordova.camera"]); - run = s.run("cordova", "build", "--settings", "settings.json"); + run = s.run("run", "firefoxos"); run.waitSecs(20); - run.expectExit(0); checkCordovaPlugins(s, ["org.apache.cordova.camera", "com.phonegap.plugins.facebookconnect", "org.apache.cordova.file"]); + // Remove a plugin run = s.run("remove", "contains-cordova-plugin"); run.match("removed"); - run = s.run("cordova", "build"); - run.waitSecs(10); - run.expectExit(0); checkCordovaPlugins(s, ["org.apache.cordova.camera"]); });