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 2015/09/02 13:31:39 UTC

[1/5] cordova-lib git commit: CB-9597 Initial Implementation of PlatformApiPoly

Repository: cordova-lib
Updated Branches:
  refs/heads/master b0c196510 -> 07271a5c6


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/util/xml-helpers.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/util/xml-helpers.js b/cordova-lib/src/util/xml-helpers.js
index 990f9f7..8b02989 100644
--- a/cordova-lib/src/util/xml-helpers.js
+++ b/cordova-lib/src/util/xml-helpers.js
@@ -192,3 +192,75 @@ function findInsertIdx(children, after) {
     //add to the beginning if no matching nodes are found
     return typeof foundIndex === 'undefined' ? 0 : foundIndex+1;
 }
+
+var BLACKLIST = ['platform', 'feature','plugin','engine'];
+var SINGLETONS = ['content', 'author'];
+function mergeXml(src, dest, platform, clobber) {
+    // Do nothing for blacklisted tags.
+    if (BLACKLIST.indexOf(src.tag) != -1) return;
+
+    //Handle attributes
+    Object.getOwnPropertyNames(src.attrib).forEach(function (attribute) {
+        if (clobber || !dest.attrib[attribute]) {
+            dest.attrib[attribute] = src.attrib[attribute];
+        }
+    });
+    //Handle text
+    if (src.text && (clobber || !dest.text)) {
+        dest.text = src.text;
+    }
+    //Handle platform
+    if (platform) {
+        src.findall('platform[@name="' + platform + '"]').forEach(function (platformElement) {
+            platformElement.getchildren().forEach(mergeChild);
+        });
+    }
+
+    //Handle children
+    src.getchildren().forEach(mergeChild);
+
+    function mergeChild (srcChild) {
+        var srcTag = srcChild.tag,
+            destChild = new et.Element(srcTag),
+            foundChild,
+            query = srcTag + '',
+            shouldMerge = true;
+
+        if (BLACKLIST.indexOf(srcTag) === -1) {
+            if (SINGLETONS.indexOf(srcTag) !== -1) {
+                foundChild = dest.find(query);
+                if (foundChild) {
+                    destChild = foundChild;
+                    dest.remove(destChild);
+                }
+            } else {
+                //Check for an exact match and if you find one don't add
+                Object.getOwnPropertyNames(srcChild.attrib).forEach(function (attribute) {
+                    query += '[@' + attribute + '="' + srcChild.attrib[attribute] + '"]';
+                });
+                var foundChildren = dest.findall(query);
+                for(var i = 0; i < foundChildren.length; i++) {
+                    foundChild = foundChildren[i];
+                    if (foundChild && textMatch(srcChild, foundChild) && (Object.keys(srcChild.attrib).length==Object.keys(foundChild.attrib).length)) {
+                        destChild = foundChild;
+                        dest.remove(destChild);
+                        shouldMerge = false;
+                        break;
+                    }
+                }
+            }
+
+            mergeXml(srcChild, destChild, platform, clobber && shouldMerge);
+            dest.append(destChild);
+        }
+    }
+}
+
+// Expose for testing.
+module.exports.mergeXml = mergeXml;
+
+function textMatch(elm1, elm2) {
+    var text1 = elm1.text ? elm1.text.replace(/\s+/, '') : '',
+        text2 = elm2.text ? elm2.text.replace(/\s+/, '') : '';
+    return (text1 === '' || text1 === text2);
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[2/5] cordova-lib git commit: CB-9597 Initial Implementation of PlatformApiPoly

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/platforms/PlatformApiPoly.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/platforms/PlatformApiPoly.js b/cordova-lib/src/platforms/PlatformApiPoly.js
new file mode 100644
index 0000000..183bc4d
--- /dev/null
+++ b/cordova-lib/src/platforms/PlatformApiPoly.js
@@ -0,0 +1,706 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var unorm = require('unorm');
+var shell = require('shelljs');
+var semver = require('semver');
+
+var superspawn = require('../cordova/superspawn');
+var xmlHelpers = require('../util/xml-helpers');
+var common = require('../plugman/platforms/common');
+var knownPlatforms = require('./platforms');
+var CordovaError = require('../CordovaError');
+var PluginInfo = require('../PluginInfo');
+var ConfigParser = require('../configparser/ConfigParser');
+var PlatformJson = require('../plugman/util/PlatformJson');
+var ActionStack = require('../plugman/util/action-stack');
+var PlatformMunger = require('../plugman/util/config-changes').PlatformMunger;
+var PluginInfoProvider = require('../PluginInfoProvider');
+
+/**
+ * Class, that acts as abstraction over particular platform. Encapsulates the
+ *   platform's properties and methods.
+ *
+ * Platform that implements own PlatformApi instance _should implement all
+ *   prototype methods_ of this class to be fully compatible with cordova-lib.
+ *
+ * The PlatformApi instance also should define the following field:
+ *
+ * * platform: String that defines a platform name.
+ */
+function PlatformApiPoly(platform, platformRootDir) {
+    if (!platform) throw new CordovaError('\'platform\' argument is missing');
+    if (!platformRootDir) throw new CordovaError('platformRootDir argument is missing');
+
+    this.root = platformRootDir;
+    this.platform = platform;
+
+    if (!(platform in knownPlatforms))
+        throw new CordovaError('Unknown platform ' + platform);
+
+    var ParserConstructor = require(knownPlatforms[platform].parser_file);
+    this._parser = new ParserConstructor(this.root);
+    this._handler = require(knownPlatforms[platform].handler_file);
+
+    this._platformJson = PlatformJson.load(this.root, platform);
+    this._pluginInfoProvider = new PluginInfoProvider();
+    this._munger = new PlatformMunger(platform, this.root, this._platformJson, this._pluginInfoProvider);
+
+    this._config = new ConfigParser(this.getPlatformInfo().locations.configXml);
+}
+
+/**
+ * Installs platform to specified directory and creates a platform project.
+ *
+ * @param  {CordovaProject} cordovaProject A CordovaProject instance, that defines a
+ *   project structure and configuration, that should be applied to new platform
+ *   (contains platform's target location and ConfigParser instance for
+ *   project's config). This argument is optional and if not defined, this means
+ *   that platform is used as standalone project and is not a part of cordova
+ *   project.
+ * @param  {Object}  options  An options object. The most common options are:
+ * @param  {String}  options.customTemplate  A path to custom template, that
+ *   should override the default one from platform.
+ * @param  {Boolean}  options.link  Flag that indicates that platform's sources
+ *   will be linked to installed platform instead of copying.
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ *   instance or rejected with CordovaError.
+ */
+PlatformApiPoly.createPlatform = function (cordovaProject, options) {
+    if (!options || !options.platformDetails)
+        return Q.reject(CordovaError('Failed to find platform\'s \'create\' script. ' +
+            'Either \'options\' parameter or \'platformDetails\' option is missing'));
+
+    var command = path.join(options.platformDetails.libDir, 'bin', 'create');
+    var commandArguments = getCreateArgs(cordovaProject, options);
+
+    return superspawn.spawn(command, commandArguments,
+        { printCommand: true, stdio: 'inherit', chmod: true })
+    .then(function () {
+        var destination = path.join(cordovaProject.locations.platforms, options.platformDetails.platform);
+        var platformApi = knownPlatforms
+            .getPlatformApi(options.platformDetails.platform, destination);
+        copyCordovaSrc(options.platformDetails.libDir, platformApi.getPlatformInfo());
+        return platformApi;
+    });
+};
+
+/**
+ * Updates already installed platform.
+ *
+ * @param   {CordovaProject}  cordovaProject  A CordovaProject instance, that
+ *   defines a project structure and configuration, that should be applied to
+ *   new platform (contains platform's target location and ConfigParser instance
+ *   for project's config). This argument is optional and if not defined, this
+ *   means that platform is used as standalone project and is not a part of
+ *   cordova project.
+ * @param  {Object}  options  An options object. The most common options are:
+ * @param  {String}  options.customTemplate  A path to custom template, that
+ *   should override the default one from platform.
+ * @param  {Boolean}  options.link  Flag that indicates that platform's sources
+ *   will be linked to installed platform instead of copying.
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ *   instance or rejected with CordovaError.
+ */
+PlatformApiPoly.updatePlatform = function (cordovaProject, options) {
+    if (!options || !options.platformDetails)
+        return Q.reject(CordovaError('Failed to find platform\'s \'create\' script. ' +
+            'Either \'options\' parameter or \'platformDetails\' option is missing'));
+
+    var command = path.join(options.platformDetails.libDir, 'bin', 'update');
+    var destination = path.join(cordovaProject.locations.platforms, options.platformDetails.platform);
+
+    return superspawn.spawn(command, [destination],
+        { printCommand: true, stdio: 'inherit', chmod: true })
+    .then(function () {
+        var platformApi = knownPlatforms
+            .getPlatformApi(options.platformDetails.platform, destination);
+        copyCordovaSrc(options.platformDetails.libDir, platformApi.getPlatformInfo());
+        return platformApi;
+    });
+};
+
+/**
+ * Gets a CordovaPlatform object, that represents the platform structure.
+ *
+ * @return  {CordovaPlatform}  A structure that contains the description of
+ *   platform's file structure and other properties of platform.
+ */
+PlatformApiPoly.prototype.getPlatformInfo = function () {
+    var self = this;
+    var result = {};
+    result.locations = {
+        www: self._parser.www_dir(),
+        platformWww: path.join(self.root, 'platform_www'),
+        configXml: self._parser.config_xml(),
+        // NOTE: Due to platformApi spec we need to return relative paths here
+        cordovaJs: path.relative(self.root, self._parser.cordovajs_path.call(self.parser, self.root)),
+        cordovaJsSrc: path.relative(self.root, self._parser.cordovajs_src_path.call(self.parser, self.root))
+    };
+    result.root = self.root;
+    result.name = self.platform;
+    result.version = knownPlatforms[self.platform].version;
+    result.projectConfig = self._config;
+
+    return result;
+};
+
+/**
+ * Updates installed platform with provided www assets and new app
+ *   configuration. This method is required for CLI workflow and will be called
+ *   each time before build, so the changes, made to app configuration and www
+ *   code, will be applied to platform.
+ *
+ * @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
+ *   project structure and configuration, that should be applied to platform
+ *   (contains project's www location and ConfigParser instance for project's
+ *   config).
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError instance.
+ */
+PlatformApiPoly.prototype.prepare = function (cordovaProject) {
+
+    // First cleanup current config and merge project's one into own
+    var defaultConfig = path.join(this.root, 'cordova', 'defaults.xml');
+    var ownConfig = this._config.path;
+    var sourceCfg = cordovaProject.projectConfig.path;
+    // If defaults.xml is present, overwrite platform config.xml with it.
+    // Otherwise save whatever is there as defaults so it can be
+    // restored or copy project config into platform if none exists.
+    if (fs.existsSync(defaultConfig)) {
+        // events.emit('verbose', 'Generating config.xml from defaults for platform "' + this.platform + '"');
+        shell.cp('-f', defaultConfig, ownConfig);
+        this._config = new ConfigParser(ownConfig);
+    } else if (fs.existsSync(ownConfig)) {
+        shell.cp('-f', ownConfig, defaultConfig);
+    } else {
+        shell.cp('-f', sourceCfg.path, ownConfig);
+        this._config = new ConfigParser(ownConfig);
+    }
+
+    xmlHelpers.mergeXml(cordovaProject.projectConfig.doc.getroot(),
+        this._config.doc.getroot(), this.platform, true);
+    // CB-6976 Windows Universal Apps. For smooth transition and to prevent mass api failures
+    // we allow using windows8 tag for new windows platform
+    if (this.platform == 'windows') {
+        xmlHelpers.mergeXml(cordovaProject.projectConfig.doc.getroot(),
+            this._config.doc.getroot(), 'windows8', true);
+    }
+    this._config.write();
+
+    // Update own www dir with project's www assets and plugins' assets and js-files
+    this._parser.update_www(cordovaProject.locations.www);
+
+    this._munger.reapply_global_munge().save_all();
+
+    // update project according to config.xml changes.
+    return this._parser.update_project(cordovaProject.projectConfig);
+};
+
+/**
+ * Installs a new plugin into platform. This method only copies non-www files
+ *   (sources, libs, etc.) to platform. It also doesn't resolves the
+ *   dependencies of plugin. Both of handling of www files, such as assets and
+ *   js-files and resolving dependencies are the responsibility of caller.
+ *
+ * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
+ *   that will be installed.
+ * @param  {Object}  installOptions  An options object. Possible options below:
+ * @param  {Boolean}  installOptions.link: Flag that specifies that plugin
+ *   sources will be symlinked to app's directory instead of copying (if
+ *   possible).
+ * @param  {Object}  installOptions.variables  An object that represents
+ *   variables that will be used to install plugin. See more details on plugin
+ *   variables in documentation:
+ *   https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError instance.
+ */
+PlatformApiPoly.prototype.addPlugin = function (plugin, installOptions) {
+
+    if (!plugin || !(plugin instanceof PluginInfo))
+        return Q.reject('The parameter is incorrect. The first parameter ' +
+            'should be valid PluginInfo instance');
+
+    installOptions = installOptions || {};
+    installOptions.variables = installOptions.variables || {};
+
+    var self = this;
+    var actions = new ActionStack();
+    var projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root);
+
+    // gather all files needs to be handled during install
+    plugin.getFilesAndFrameworks(this.platform)
+        .concat(plugin.getAssets(this.platform))
+        .concat(plugin.getJsModules(this.platform))
+    .forEach(function(item) {
+        actions.push(actions.createAction(
+            self._getInstaller(item.itemType), [item, plugin.dir, plugin.id, installOptions, projectFile],
+            self._getUninstaller(item.itemType), [item, plugin.dir, plugin.id, installOptions, projectFile]));
+    });
+
+    // run through the action stack
+    return actions.process(this.platform, this.root)
+    .then(function () {
+        if (projectFile) {
+            projectFile.write();
+        }
+
+        // Add PACKAGE_NAME variable into vars
+        if (!installOptions.variables.PACKAGE_NAME) {
+            installOptions.variables.PACKAGE_NAME = self._handler.package_name(self.root);
+        }
+
+        self._munger
+            // Ignore passed `is_top_level` option since platform itself doesn't know
+            // anything about managing dependencies - it's responsibility of caller.
+            .add_plugin_changes(plugin, installOptions.variables, /*is_top_level=*/true, /*should_increment=*/true)
+            .save_all();
+
+        var targetDir = installOptions.usePlatformWww ?
+            self.getPlatformInfo().locations.platformWww :
+            self.getPlatformInfo().locations.www;
+
+        self._addModulesInfo(plugin, targetDir);
+    });
+};
+
+/**
+ * Removes an installed plugin from platform.
+ *
+ * Since method accepts PluginInfo instance as input parameter instead of plugin
+ *   id, caller shoud take care of managing/storing PluginInfo instances for
+ *   future uninstalls.
+ *
+ * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
+ *   that will be installed.
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError instance.
+ */
+PlatformApiPoly.prototype.removePlugin = function (plugin, uninstallOptions) {
+
+    var self = this;
+    var actions = new ActionStack();
+    var projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root);
+
+    // queue up plugin files
+    plugin.getFilesAndFrameworks(this.platform)
+        .concat(plugin.getAssets(this.platform))
+        .concat(plugin.getJsModules(this.platform))
+    .filter(function (item) {
+        // CB-5238 Skip (don't uninstall) non custom frameworks.
+        return !(item.itemType == 'framework' && !item.custom);
+    }).forEach(function(item) {
+        actions.push(actions.createAction(
+            self._getUninstaller(item.itemType), [item, plugin.dir, plugin.id, uninstallOptions, projectFile],
+            self._getInstaller(item.itemType), [item, plugin.dir, plugin.id, uninstallOptions, projectFile]));
+    });
+
+    // run through the action stack
+    return actions.process(this.platform, this.root)
+    .then(function() {
+        if (projectFile) {
+            projectFile.write();
+        }
+
+        self._munger
+            // Ignore passed `is_top_level` option since platform itself doesn't know
+            // anything about managing dependencies - it's responsibility of caller.
+            .remove_plugin_changes(plugin, /*is_top_level=*/true)
+            .save_all();
+
+        var targetDir = uninstallOptions.usePlatformWww ?
+            self.getPlatformInfo().locations.platformWww :
+            self.getPlatformInfo().locations.www;
+
+        self._removeModulesInfo(plugin, targetDir);
+        // Remove stale plugin directory
+        // TODO: this should be done by plugin files uninstaller
+        shell.rm('-rf', path.resolve(self.root, 'Plugins', plugin.id));
+    });
+};
+
+PlatformApiPoly.prototype.updatePlugin = function (plugin, updateOptions) {
+    var self = this;
+
+    // Set up assets installer to copy asset files into platform_www dir instead of www
+    updateOptions = updateOptions || {};
+    updateOptions.usePlatformWww = true;
+
+    return this.removePlugin(plugin, updateOptions)
+    .then(function () {
+        return  self.addPlugin(plugin, updateOptions);
+    });
+};
+
+/**
+ * Builds an application package for current platform.
+ *
+ * @param  {Object}  buildOptions  A build options. This object's structure is
+ *   highly depends on platform's specific. The most common options are:
+ * @param  {Boolean}  buildOptions.debug  Indicates that packages should be
+ *   built with debug configuration. This is set to true by default unless the
+ *   'release' option is not specified.
+ * @param  {Boolean}  buildOptions.release  Indicates that packages should be
+ *   built with release configuration. If not set to true, debug configuration
+ *   will be used.
+ * @param   {Boolean}  buildOptions.device  Specifies that built app is intended
+ *   to run on device
+ * @param   {Boolean}  buildOptions.emulator: Specifies that built app is
+ *   intended to run on emulator
+ * @param   {String}  buildOptions.target  Specifies the device id that will be
+ *   used to run built application.
+ * @param   {Boolean}  buildOptions.nobuild  Indicates that this should be a
+ *   dry-run call, so no build artifacts will be produced.
+ * @param   {String[]}  buildOptions.archs  Specifies chip architectures which
+ *   app packages should be built for. List of valid architectures is depends on
+ *   platform.
+ * @param   {String}  buildOptions.buildConfig  The path to build configuration
+ *   file. The format of this file is depends on platform.
+ * @param   {String[]} buildOptions.argv Raw array of command-line arguments,
+ *   passed to `build` command. The purpose of this property is to pass a
+ *   platform-specific arguments, and eventually let platform define own
+ *   arguments processing logic.
+ *
+ * @return {Promise<Object[]>} A promise either fulfilled with an array of build
+ *   artifacts (application packages) if package was built successfully,
+ *   or rejected with CordovaError. The resultant build artifact objects is not
+ *   strictly typed and may conatin arbitrary set of fields as in sample below.
+ *
+ *     {
+ *         architecture: 'x86',
+ *         buildType: 'debug',
+ *         path: '/path/to/build',
+ *         type: 'app'
+ *     }
+ *
+ * The return value in most cases will contain only one item but in some cases
+ *   there could be multiple items in output array, e.g. when multiple
+ *   arhcitectures is specified.
+ */
+PlatformApiPoly.prototype.build = function(buildOptions) {
+    var command = path.join(this.root, 'cordova', 'build');
+    var commandArguments = getBuildArgs(buildOptions);
+    return superspawn.spawn(command, commandArguments, {
+        printCommand: true, stdio: 'inherit', chmod: true });
+};
+
+/**
+ * Builds an application package for current platform and runs it on
+ *   specified/default device. If no 'device'/'emulator'/'target' options are
+ *   specified, then tries to run app on default device if connected, otherwise
+ *   runs the app on emulator.
+ *
+ * @param   {Object}  runOptions  An options object. The structure is the same
+ *   as for build options.
+ *
+ * @return {Promise} A promise either fulfilled if package was built and ran
+ *   successfully, or rejected with CordovaError.
+ */
+PlatformApiPoly.prototype.run = function(runOptions) {
+    var command = path.join(this.root, 'cordova', 'run');
+    var commandArguments = getBuildArgs(runOptions);
+    return superspawn.spawn(command, commandArguments, {
+        printCommand: true, stdio: 'inherit', chmod: true });
+};
+
+/**
+ * Cleans out the build artifacts from platform's directory.
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError.
+ */
+PlatformApiPoly.prototype.clean = function() {
+    var cmd = path.join(this.root, 'cordova', 'clean');
+    return superspawn.spawn(cmd, [], { printCommand: true, stdio: 'inherit', chmod: true });
+};
+
+/**
+ * Performs a requirements check for current platform. Each platform defines its
+ *   own set of requirements, which should be resolved before platform can be
+ *   built successfully.
+ *
+ * @return  {Promise<Requirement[]>}  Promise, resolved with set of Requirement
+ *   objects for current platform.
+ */
+PlatformApiPoly.prototype.requirements = function() {
+    var modulePath = path.join(this.root, 'cordova', 'lib', 'check_reqs');
+    try {
+        return require(modulePath).check_all();
+    } catch (e) {
+        var errorMsg = 'Failed to check requirements for ' + this.platform + ' platform. ' +
+            'check_reqs module is missing for platfrom. Skipping it...';
+        return Q.reject(errorMsg);
+    }
+};
+
+module.exports = PlatformApiPoly;
+
+/**
+ * Converts arguments, passed to createPlatform to command-line args to
+ *   'bin/create' script for specific platform.
+ *
+ * @param   {ProjectInfo}  project  A current project information. The vauest
+ *   which this method interested in are project.config - config.xml abstraction
+ *   - and platformsLocation - to get install destination.
+ * @param   {Object}       options  Set of properties for create script.
+ *
+ * @return  {String[]}     An array or arguments which can be passed to
+ *   'bin/create'.
+ */
+function getCreateArgs(project, options) {
+    var platformName = options.platformDetails.platform;
+    var platformVersion = options.platformDetails.version;
+
+    var args = [];
+    args.push(path.join(project.locations.platforms, platformName)); // output
+    args.push(project.projectConfig.packageName().replace(/[^\w.]/g,'_'));
+    // CB-6992 it is necessary to normalize characters
+    // because node and shell scripts handles unicode symbols differently
+    // We need to normalize the name to NFD form since iOS uses NFD unicode form
+    args.push(platformName == 'ios' ? unorm.nfd(project.projectConfig.name()) : project.projectConfig.name());
+
+    if (options.customTemplate) {
+        args.push(options.customTemplate);
+    }
+
+    if (/android|ios/.exec(platformName) &&
+        semver.gt(platformVersion, '3.3.0')) args.push('--cli');
+
+    if (options.link) args.push('--link');
+
+    if (platformName === 'android' && semver.gte(platformVersion, '4.0.0-dev')) {
+        var activityName = project.projectConfig.android_activityName();
+        if (activityName) {
+            args.push('--activity-name', activityName.replace(/\W/g, ''));
+        }
+    }
+
+    return args;
+}
+
+/**
+ * Reconstructs the buildOptions tat will be passed along to platform scripts.
+ *   This is an ugly temporary fix. The code spawning or otherwise calling into
+ *   platform code should be dealing with this based on the parsed args object.
+ *
+ * @param   {Object}  options  A build options set, passed to `build` method
+ *
+ * @return  {String[]}         An array or arguments which can be passed to
+ *   `create` build script.
+ */
+function getBuildArgs(options) {
+    // if no options passed, empty object will be returned
+    if (!options) return [];
+
+    var downstreamArgs = [];
+    var argNames =[
+        'debug',
+        'release',
+        'device',
+        'emulator',
+        'nobuild',
+        'list'
+    ];
+
+    argNames.forEach(function(flag) {
+        if (options[flag]) {
+            downstreamArgs.push('--' + flag);
+        }
+    });
+
+    if (options.buildConfig) {
+        downstreamArgs.push('--buildConfig=' + options.buildConfig);
+    }
+    if (options.target) {
+        downstreamArgs.push('--target=' + options.target);
+    }
+    if (options.archs) {
+        downstreamArgs.push('--archs=' + options.archs);
+    }
+
+    var unparsedArgs = options.argv || [];
+    return downstreamArgs.concat(unparsedArgs);
+}
+
+/**
+ * Removes the specified modules from list of installed modules and updates
+ *   platform_json and cordova_plugins.js on disk.
+ *
+ * @param   {PluginInfo}  plugin  PluginInfo instance for plugin, which modules
+ *   needs to be added.
+ * @param   {String}  targetDir  The directory, where updated cordova_plugins.js
+ *   should be written to.
+ */
+PlatformApiPoly.prototype._addModulesInfo = function(plugin, targetDir) {
+    var installedModules = this._platformJson.root.modules || [];
+
+    var installedPaths = installedModules.map(function (installedModule) {
+        return installedModule.file;
+    });
+
+    var modulesToInstall = plugin.getJsModules(this.platform)
+    .filter(function (moduleToInstall) {
+        return installedPaths.indexOf(moduleToInstall.file) === -1;
+    }).map(function (moduleToInstall) {
+        var moduleName = plugin.id + '.' + ( moduleToInstall.name || moduleToInstall.src.match(/([^\/]+)\.js/)[1] );
+        var obj = {
+            file: ['plugins', plugin.id, moduleToInstall.src].join('/'),
+            id: moduleName
+        };
+        if (moduleToInstall.clobbers.length > 0) {
+            obj.clobbers = moduleToInstall.clobbers.map(function(o) { return o.target; });
+        }
+        if (moduleToInstall.merges.length > 0) {
+            obj.merges = moduleToInstall.merges.map(function(o) { return o.target; });
+        }
+        if (moduleToInstall.runs) {
+            obj.runs = true;
+        }
+
+        return obj;
+    });
+
+    this._platformJson.root.modules = installedModules.concat(modulesToInstall);
+    this._writePluginModules(targetDir);
+    this._platformJson.save();
+};
+
+/**
+ * Removes the specified modules from list of installed modules and updates
+ *   platform_json and cordova_plugins.js on disk.
+ *
+ * @param   {PluginInfo}  plugin  PluginInfo instance for plugin, which modules
+ *   needs to be removed.
+ * @param   {String}  targetDir  The directory, where updated cordova_plugins.js
+ *   should be written to.
+ */
+PlatformApiPoly.prototype._removeModulesInfo = function(plugin, targetDir) {
+    var installedModules = this._platformJson.root.modules || [];
+    var modulesToRemove = plugin.getJsModules(this.platform)
+    .map(function (jsModule) {
+        return  ['plugins', plugin.id, jsModule.src].join('/');
+    });
+
+    var updatedModules = installedModules
+    .filter(function (installedModule) {
+        return (modulesToRemove.indexOf(installedModule.file) === -1);
+    });
+
+    this._platformJson.root.modules = updatedModules;
+    this._writePluginModules(targetDir);
+    this._platformJson.save();
+};
+
+/**
+ * Fetches all installed modules, generates cordova_plugins contents and writes
+ *   it to file.
+ *
+ * @param   {String}  targetDir  Directory, where write cordova_plugins.js to.
+ *   Ususally it is either <platform>/www or <platform>/platform_www
+ *   directories.
+ */
+PlatformApiPoly.prototype._writePluginModules = function (targetDir) {
+    var self = this;
+    // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js
+    var final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n';
+    final_contents += 'module.exports = ' + JSON.stringify(this._platformJson.root.modules, null, '    ') + ';\n';
+    final_contents += 'module.exports.metadata = \n';
+    final_contents += '// TOP OF METADATA\n';
+
+    var pluginMetadata = Object.keys(this._platformJson.root.installed_plugins)
+    .reduce(function (metadata, plugin) {
+        metadata[plugin] = self._platformJson.root.installed_plugins[plugin].version;
+        return metadata;
+    }, {});
+
+    final_contents += JSON.stringify(pluginMetadata, null, '    ') + '\n';
+    final_contents += '// BOTTOM OF METADATA\n';
+    final_contents += '});'; // Close cordova.define.
+
+    shell.mkdir('-p', targetDir);
+    fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8');
+};
+
+PlatformApiPoly.prototype._getInstaller = function(type) {
+    var self = this;
+    return function (item, plugin_dir, plugin_id, options, project) {
+        var installer = self._handler[type] || common[type];
+
+        var wwwDest = options.usePlatformWww ?
+            self.getPlatformInfo().locations.platformWww :
+            self._handler.www_dir(self.root);
+
+        var installerArgs = type === 'asset' ? [wwwDest] :
+            type === 'js-module' ? [plugin_id, wwwDest]:
+            [self.root, plugin_id, options, project];
+
+        installer.install.apply(null, [item, plugin_dir].concat(installerArgs));
+    };
+};
+
+PlatformApiPoly.prototype._getUninstaller = function(type) {
+    var self = this;
+    return function (item, plugin_dir, plugin_id, options, project) {
+        var uninstaller = self._handler[type] || common[type];
+
+        var wwwDest = options.usePlatformWww ?
+            self.getPlatformInfo().locations.platformWww :
+            self._handler.www_dir(self.root);
+
+        var uninstallerArgs = (type === 'asset' || type === 'js-module') ? [wwwDest, plugin_id] :
+            [self.root, plugin_id, options, project];
+
+        uninstaller.uninstall.apply(null, [item].concat(uninstallerArgs));
+    };
+};
+
+/**
+ * Copies cordova.js itself and cordova-js source into installed/updated
+ *   platform's `platform_www` directory.
+ *
+ * @param   {String}  sourceLib    Path to platform library. Required to acquire
+ *   cordova-js sources.
+ * @param   {PlatformInfo}  platformInfo  PlatformInfo structure, required for
+ *   detecting copied files destination.
+ */
+function copyCordovaSrc(sourceLib, platformInfo) {
+    // Copy the cordova.js file to platforms/<platform>/platform_www/
+    // The www dir is nuked on each prepare so we keep cordova.js in platform_www
+    shell.mkdir('-p', platformInfo.locations.platformWww);
+    shell.cp('-f', path.join(platformInfo.locations.www, 'cordova.js'),
+        path.join(platformInfo.locations.platformWww, 'cordova.js'));
+
+    // Copy cordova-js-src directory into platform_www directory.
+    // We need these files to build cordova.js if using browserify method.
+    var cordovaJsSrcPath = path.resolve(sourceLib, platformInfo.locations.cordovaJsSrc);
+
+    //only exists for platforms that have shipped cordova-js-src directory
+    if(fs.existsSync(cordovaJsSrcPath)) {
+        shell.cp('-rf', cordovaJsSrcPath, platformInfo.locations.platformWww);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/platforms/platforms.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/platforms/platforms.js b/cordova-lib/src/platforms/platforms.js
index cc0e492..d71570f 100644
--- a/cordova-lib/src/platforms/platforms.js
+++ b/cordova-lib/src/platforms/platforms.js
@@ -17,89 +17,47 @@
     under the License.
 */
 
+var path = require('path');
+var util = require('../cordova/util');
 var platforms = require('./platformsConfig.json');
 
-// Remove this block soon. The parser property is no longer used in
-// cordova-lib but some downstream tools still use it.
-var addModuleProperty = require('../cordova/util').addModuleProperty;
-Object.keys(platforms).forEach(function(key) {
-    var obj = platforms[key];
-    if (obj.parser_file) {
-        addModuleProperty(module, 'parser', obj.parser_file, false, obj);
-    }
-});
-
-
 // Avoid loading the same platform projects more than once (identified by path)
-var cachedProjects = {};
-
-var PARSER_PUBLIC_METHODS = [
-    'config_xml',
-    'cordovajs_path',
-    'cordovajs_src_path',
-    'update_from_config',
-    'update_project',
-    'update_www',
-    'www_dir',
-];
-
-var HANDLER_PUBLIC_METHODS = [
-    'package_name',
-    'parseProjectFile',
-    'purgeProjectFileCache',
-];
-
+var cachedApis = {};
 
-// A single class that exposes functionality from platform specific files from
-// both places cordova/metadata and plugman/platforms. Hopefully, to be soon
-// replaced by real unified platform specific classes.
-function PlatformProjectAdapter(platform, platformRootDir) {
-    var self = this;
-    self.root = platformRootDir;
-    self.platform = platform;
-    var ParserConstructor = require(platforms[platform].parser_file);
-    self.parser = new ParserConstructor(platformRootDir);
-    self.handler = require(platforms[platform].handler_file);
+// getPlatformApi() should be the only method of instantiating the
+// PlatformProject classes for now.
+function getPlatformApi(platform, platformRootDir) {
 
-    // Expose all public methods from the parser and handler, properly bound.
-    PARSER_PUBLIC_METHODS.forEach(function(method) {
-        self[method] = self.parser[method].bind(self.parser);
-    });
+    // if platformRootDir is not specified, try to detect it first
+    if (!platformRootDir) {
+        var projectRootDir = util.isCordova();
+        platformRootDir = projectRootDir && path.join(projectRootDir, 'platforms', platform);
+    }
 
-    HANDLER_PUBLIC_METHODS.forEach(function(method) {
-        if (self.handler[method]) {
-            self[method] = self.handler[method].bind(self.handler);
-        }
-    });
+    if (!platformRootDir) {
+        // If platformRootDir is still undefined, then we're probably is not inside of cordova project
+        throw new Error('Current location is not a Cordova project');
+    }
 
-    self.getInstaller = function(type) {
-        function installWrapper(item, plugin_dir, plugin_id, options, project) {
-            self.handler[type].install(item, plugin_dir, self.root, plugin_id, options, project);
-        }
-        return installWrapper;
-    };
+    var cached = cachedApis[platformRootDir];
+    if (cached && cached.platform == platform) return cached;
 
-    self.getUninstaller = function(type) {
-        function uninstallWrapper(item, plugin_id, options, project) {
-            self.handler[type].uninstall(item, self.root, plugin_id, options, project);
-        }
-        return uninstallWrapper;
-    };
-}
+    if (!platforms[platform]) throw new Error('Unknown platform ' + platform);
 
-// getPlatformProject() should be the only method of instantiating the
-// PlatformProject classes for now.
-function getPlatformProject(platform, platformRootDir) {
-    var cached = cachedProjects[platformRootDir];
-    if (cached && cached.platform == platform) {
-        return cachedProjects[platformRootDir];
-    } else if (platforms[platform]) {
-        var adapter = new PlatformProjectAdapter(platform, platformRootDir);
-        cachedProjects[platformRootDir] = adapter;
-        return adapter;
-    } else {
-        throw new Error('Unknown platform ' + platform);
+    var PlatformApi;
+    try {
+        // First we need to find whether platform exposes its' API via js module
+        // If it has, then we have to require it and extend BasePlatformApi
+        // with platform's API.
+        var platformApiModule = path.join(platformRootDir, 'cordova', 'Api.js');
+        PlatformApi = require(platformApiModule);
+    } catch (err) {
+        PlatformApi = require('./PlatformApiPoly');
     }
+
+    var platformApi = new PlatformApi(platform, platformRootDir);
+    cachedApis[platformRootDir] = platformApi;
+    return platformApi;
 }
 
 module.exports = platforms;
@@ -107,6 +65,5 @@ module.exports = platforms;
 // We don't want these methods to be enumerable on the platforms object, because we expect enumerable properties of the
 // platforms object to be platforms.
 Object.defineProperties(module.exports, {
-    'getPlatformProject': {value: getPlatformProject, configurable: true, writable: true},
-    'PlatformProjectAdapter': {value: PlatformProjectAdapter, configurable: true, writable: true}
+    'getPlatformApi': {value: getPlatformApi, configurable: true, writable: true}
 });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/browserify.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/browserify.js b/cordova-lib/src/plugman/browserify.js
new file mode 100644
index 0000000..8d5581b
--- /dev/null
+++ b/cordova-lib/src/plugman/browserify.js
@@ -0,0 +1,181 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+/* jshint unused:false, expr:true */
+
+var platform_modules   = require('../platforms/platforms'),
+    path               = require('path'),
+    aliasify           = require('aliasify'),
+    config_changes     = require('./util/config-changes'),
+    common             = require('./platforms/common'),
+    fs                 = require('fs'),
+    childProcess       = require('child_process'),
+    util               = require('util'),
+    events             = require('../events'),
+    plugman            = require('./plugman'),
+    bundle             = require('cordova-js/tasks/lib/bundle-browserify'),
+    writeLicenseHeader = require('cordova-js/tasks/lib/write-license-header'),
+    Q                  = require('q'),
+    computeCommitId    = require('cordova-js/tasks/lib/compute-commit-id'),
+    Readable           = require('stream').Readable;
+
+var PlatformJson = require('./util/PlatformJson');
+var PluginInfoProvider = require('../PluginInfoProvider');
+
+function generateFinalBundle(platform, libraryRelease, outReleaseFile, commitId, platformVersion) {
+    var deferred = Q.defer();
+    var outReleaseFileStream = fs.createWriteStream(outReleaseFile);
+    var time = new Date().valueOf();
+    var symbolList = null;
+
+    writeLicenseHeader(outReleaseFileStream, platform, commitId, platformVersion);
+
+    var releaseBundle = libraryRelease.bundle();
+
+    releaseBundle.pipe(outReleaseFileStream);
+
+    outReleaseFileStream.on('finish', function() {
+        var newtime = new Date().valueOf() - time;
+        plugman.emit('verbose', 'generated cordova.' + platform + '.js @ ' + commitId + ' in ' + newtime + 'ms');
+        deferred.resolve();
+        // TODO clean up all the *.browserify files
+    });
+
+    outReleaseFileStream.on('error', function(err) {
+        events.emit('log', 'error while generating cordova.js');
+        deferred.reject();
+    });
+    return deferred.promise;
+}
+
+function computeCommitIdSync() {
+    var deferred = Q.defer();
+    computeCommitId(function(cId){
+        deferred.resolve(cId);
+    });
+    return deferred.promise;
+}
+
+function getPlatformVersion(cId, project_dir) {
+    var deferred = Q.defer();
+    //run version script for each platform to get platformVersion
+    var versionPath = path.join(project_dir, '/cordova/version');
+    childProcess.exec('"' + versionPath + '"', function(err, stdout, stderr) {
+        if (err) {
+            events.emit('log', 'Error running platform version script');
+            events.emit('log', err);
+            deferred.resolve('N/A');
+        } else {
+            deferred.resolve(stdout.trim());
+        }
+    });
+    return deferred.promise;
+}
+
+module.exports = function doBrowserify (project, platformApi, pluginInfoProvider) {
+    // Process:
+    // - Do config munging by calling into config-changes module
+    // - List all plugins in plugins_dir
+    // - Load and parse their plugin.xml files.
+    // - Skip those without support for this platform. (No <platform> tags means JS-only!)
+    // - Build a list of all their js-modules, including platform-specific js-modules.
+    // - 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.
+    // This file is not really used. Maybe cordova app harness
+    var platform = platformApi.platform;
+    events.emit('verbose', 'Preparing ' + platform + ' browserify project');
+    pluginInfoProvider = pluginInfoProvider || new PluginInfoProvider(); // Allow null for backwards-compat.
+    var platformJson = PlatformJson.load(project.locations.plugins, platform);
+    var wwwDir = platformApi.getPlatformInfo().locations.www;
+
+    var commitId;
+    return computeCommitIdSync()
+    .then(function(cId){
+        commitId = cId;
+        return getPlatformVersion(commitId, platformApi.root);
+    }).then(function(platformVersion){
+        var libraryRelease = bundle(platform, false, commitId, platformVersion);
+
+        var pluginMetadata = {};
+        var modulesMetadata = [];
+
+        var plugins = Object.keys(platformJson.root.installed_plugins).concat(Object.keys(platformJson.root.dependent_plugins));
+        events.emit('verbose', 'Iterating over installed plugins:', plugins);
+        plugins.forEach(function (plugin) {
+            var pluginDir = path.join(project.locations.plugins, plugin);
+            var pluginInfo = pluginInfoProvider.get(pluginDir);
+            // pluginMetadata is a mapping from plugin IDs to versions.
+            pluginMetadata[pluginInfo.id] = pluginInfo.version;
+
+            // Copy www assets described in <asset> tags.
+            pluginInfo.getAssets(platform)
+            .forEach(function(asset) {
+                common.asset.install(asset, pluginDir, wwwDir);
+            });
+
+            pluginInfo.getJsModules(platform)
+            .forEach(function(jsModule) {
+                var moduleName = jsModule.name ? jsModule.name : path.basename(jsModule.src, '.js');
+                var moduleId = pluginInfo.id + '.' + moduleName;
+                var moduleMetadata = {file: jsModule.src, id: moduleId, name: moduleName};
+
+                if (jsModule.clobbers.length > 0) {
+                    moduleMetadata.clobbers = jsModule.clobbers.map(function(o) { return o.target; });
+                }
+                if (jsModule.merges.length > 0) {
+                    moduleMetadata.merges = jsModule.merges.map(function(o) { return o.target; });
+                }
+                if (jsModule.runs) {
+                    moduleMetadata.runs = true;
+                }
+
+                modulesMetadata.push(moduleMetadata);
+                libraryRelease.require(path.join(pluginDir, jsModule.src), { expose: moduleId });
+            });
+        });
+
+        events.emit('verbose', 'Writing out cordova_plugins.js...');
+
+        // Create a stream and write plugin metadata into it
+        // instead of generating intermediate file on FS
+        var cordova_plugins = new Readable();
+        cordova_plugins.push(
+            'module.exports.metadata = ' + JSON.stringify(pluginMetadata, null, 4) + ';\n' +
+            'module.exports = ' + JSON.stringify(modulesMetadata, null, 4) + ';\n', 'utf8');
+        cordova_plugins.push(null);
+
+        var bootstrap = new Readable();
+        bootstrap.push('require(\'cordova/init\');\n', 'utf8');
+        bootstrap.push(null);
+
+        var moduleAliases = modulesMetadata
+        .reduce(function (accum, meta) {
+            accum['./' + meta.name] = meta.id;
+            return accum;
+        }, {});
+
+        libraryRelease
+            .add(cordova_plugins, {file: path.join(wwwDir, 'cordova_plugins.js'), expose: 'cordova/plugin_list'})
+            .add(bootstrap)
+            .transform(aliasify, {aliases: moduleAliases});
+
+        var outReleaseFile = path.join(wwwDir, 'cordova.js');
+        return generateFinalBundle(platform, libraryRelease, outReleaseFile, commitId, platformVersion);
+    });
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/install.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/install.js b/cordova-lib/src/plugman/install.js
index b99d155..071e851 100644
--- a/cordova-lib/src/plugman/install.js
+++ b/cordova-lib/src/plugman/install.js
@@ -471,7 +471,6 @@ function tryFetchDependency(dep, install, options) {
                 dep.subdir = '';
                 return Q(url);
             }).fail(function(error){
-//console.log("Failed to resolve url='.': " + error);
                 return Q(dep.url);
             });
 
@@ -565,35 +564,27 @@ function handleInstall(actions, pluginInfo, platform, project_dir, plugins_dir,
 
     // @tests - important this event is checked spec/install.spec.js
     events.emit('verbose', 'Install start for "' + pluginInfo.id + '" on ' + platform + '.');
-    var handler = platform_modules.getPlatformProject(platform, project_dir);
-    var frameworkFiles = pluginInfo.getFrameworks(platform); // Frameworks are needed later
-    var pluginItems = pluginInfo.getFilesAndFrameworks(platform);
-
-    // queue up native stuff
-    pluginItems.forEach(function(item) {
-        actions.push(actions.createAction(handler.getInstaller(item.itemType),
-                                          [item, plugin_dir, pluginInfo.id, options],
-                                          handler.getUninstaller(item.itemType),
-                                          [item, pluginInfo.id, options]));
-    });
 
-    // run through the action stack
-    return actions.process(platform, project_dir)
-    .then(function(err) {
-        // queue up the plugin so prepare knows what to do.
-        var platformJson = PlatformJson.load(plugins_dir, platform);
-        platformJson.addInstalledPluginToPrepareQueue(pluginInfo.id, filtered_variables, options.is_top_level);
-        platformJson.save();
-        // call prepare after a successful install
-        if (options.browserify) {
-            return plugman.prepareBrowserify(project_dir, platform, plugins_dir, options.www_dir, options.is_top_level, options.pluginInfoProvider);
-        } else {
-            return plugman.prepare(project_dir, platform, plugins_dir, options.www_dir, options.is_top_level, options.pluginInfoProvider);
-        }
-    }).then (function() {
+    options.variables = filtered_variables;
+    // Set up platform to install asset files/js modules to <platform>/platform_www dir
+    // instead of <platform>/www. This is required since on each prepare platform's www dir is changed
+    // and files from 'platform_www' merged into 'www'. Thus we need to persist these
+    // files platform_www directory, so they'll be applied to www on each prepare.
+    options.usePlatformWww = true;
+
+    return platform_modules.getPlatformApi(platform, project_dir)
+    .addPlugin(pluginInfo, options)
+    .then (function() {
         events.emit('verbose', 'Install complete for ' + pluginInfo.id + ' on ' + platform + '.');
+        // Add plugin to installed list. This already done in platform,
+        // but need to be duplicated here to manage dependencies properly.
+        PlatformJson.load(plugins_dir, platform)
+            .addPlugin(pluginInfo.id, filtered_variables, options.is_top_level)
+            .save();
+
+        if (platform == 'android' && semver.gte(options.platformVersion, '4.0.0-dev') &&
+                pluginInfo.getFrameworks('platform').length > 0) {
 
-        if (platform == 'android' && semver.gte(options.platformVersion, '4.0.0-dev') && frameworkFiles.length > 0) {
             events.emit('verbose', 'Updating build files since android plugin contained <framework>');
             var buildModule;
             try {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/platforms/common.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/platforms/common.js b/cordova-lib/src/plugman/platforms/common.js
index e3d3bcd..358ceed 100644
--- a/cordova-lib/src/plugman/platforms/common.js
+++ b/cordova-lib/src/plugman/platforms/common.js
@@ -79,7 +79,11 @@ module.exports = common = {
     },
     // Sometimes we want to remove some java, and prune any unnecessary empty directories
     deleteJava:function(project_dir, destFile) {
-        var file = path.resolve(project_dir, destFile);
+        common.removeFileAndParents(project_dir, destFile, 'src');
+    },
+    removeFileAndParents:function(baseDir, destFile, stopper) {
+        stopper = stopper || '.';
+        var file = path.resolve(baseDir, destFile);
         if (!fs.existsSync(file)) return;
 
         common.removeFileF(file);
@@ -87,7 +91,7 @@ module.exports = common = {
         // check if directory is empty
         var curDir = path.dirname(file);
 
-        while(curDir !== path.resolve(project_dir, 'src')) {
+        while(curDir !== path.resolve(baseDir, stopper)) {
             if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
                 fs.rmdirSync(curDir);
                 curDir = path.resolve(curDir, '..');
@@ -119,5 +123,27 @@ module.exports = common = {
             common.removeFile(www_dir, target);
             common.removeFileF(path.resolve(www_dir, 'plugins', plugin_id));
         }
+    },
+    'js-module': {
+        install: function (jsModule, plugin_dir, plugin_id, www_dir) {
+            // Copy the plugin's files into the www directory.
+            var moduleSource = path.resolve(plugin_dir, jsModule.src);
+            var moduleName = plugin_id + '.' + (jsModule.name || path.parse(jsModule.src).name);
+
+            // Read in the file, prepend the cordova.define, and write it back out.
+            var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
+            if (moduleSource.match(/.*\.json$/)) {
+                scriptContent = 'module.exports = ' + scriptContent;
+            }
+            scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) { ' + scriptContent + '\n});\n';
+
+            var moduleDestination = path.resolve(www_dir, 'plugins', plugin_id, jsModule.src);
+            shell.mkdir('-p', path.dirname(moduleDestination));
+            fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
+        },
+        uninstall: function (jsModule, www_dir, plugin_id) {
+            var pluginRelativePath = path.join('plugins', plugin_id, jsModule.src);
+            common.removeFileAndParents(www_dir, pluginRelativePath);
+        }
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/plugman.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/plugman.js b/cordova-lib/src/plugman/plugman.js
index 7d952f5..67637ed 100644
--- a/cordova-lib/src/plugman/plugman.js
+++ b/cordova-lib/src/plugman/plugman.js
@@ -65,8 +65,7 @@ var plugman = {
 addProperty(plugman, 'install', './install', true);
 addProperty(plugman, 'uninstall', './uninstall', true);
 addProperty(plugman, 'fetch', './fetch', true);
-addProperty(plugman, 'prepare', './prepare');
-addProperty(plugman, 'prepareBrowserify', './prepare-browserify');
+addProperty(plugman, 'browserify', './browserify');
 addProperty(plugman, 'help', './help');
 addProperty(plugman, 'config', './config', true);
 addProperty(plugman, 'owner', './owner', true);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/prepare-browserify.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/prepare-browserify.js b/cordova-lib/src/plugman/prepare-browserify.js
deleted file mode 100644
index 2dfb26a..0000000
--- a/cordova-lib/src/plugman/prepare-browserify.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-/* jshint unused:false, expr:true */
-
-var platform_modules   = require('../platforms/platforms'),
-    path               = require('path'),
-    aliasify           = require('aliasify'),
-    config_changes     = require('./util/config-changes'),
-    common             = require('./platforms/common'),
-    fs                 = require('fs'),
-    childProcess       = require('child_process'),
-    shell              = require('shelljs'),
-    util               = require('util'),
-    events             = require('../events'),
-    plugman            = require('./plugman'),
-    bundle             = require('cordova-js/tasks/lib/bundle-browserify'),
-    writeLicenseHeader = require('cordova-js/tasks/lib/write-license-header'),
-    Q                  = require('q'),
-    computeCommitId    = require('cordova-js/tasks/lib/compute-commit-id'),
-    Readable           = require('stream').Readable;
-
-var PlatformJson = require('./util/PlatformJson');
-var PluginInfoProvider = require('../PluginInfoProvider');
-
-function uninstallQueuedPlugins(platformJson, wwwDir) {
-    // Check if there are any plugins queued for uninstallation, and if so, remove any of their plugin web assets loaded in
-    // via <js-module> elements
-    var plugins_to_uninstall = platformJson.root.prepare_queue.uninstalled;
-    if (plugins_to_uninstall && plugins_to_uninstall.length) {
-        var plugins_www = path.join(wwwDir, 'plugins');
-        if (fs.existsSync(plugins_www)) {
-            plugins_to_uninstall.forEach(function(plug) {
-                var id = plug.id;
-                var plugin_modules = path.join(plugins_www, id);
-                if (fs.existsSync(plugin_modules)) {
-                    events.emit('verbose', 'Removing plugins directory from www "'+plugin_modules+'"');
-                    shell.rm('-rf', plugin_modules);
-                }
-            });
-        }
-    }
-}
-
-function generateFinalBundle(platform, libraryRelease, outReleaseFile, commitId, platformVersion) {
-    var deferred = Q.defer();
-    var outReleaseFileStream = fs.createWriteStream(outReleaseFile);
-    var time = new Date().valueOf();
-    var symbolList = null;
-
-    writeLicenseHeader(outReleaseFileStream, platform, commitId, platformVersion);
-
-    var releaseBundle = libraryRelease.bundle();
-
-    releaseBundle.pipe(outReleaseFileStream);
-
-    outReleaseFileStream.on('finish', function() {
-        var newtime = new Date().valueOf() - time;
-        plugman.emit('verbose', 'generated cordova.' + platform + '.js @ ' + commitId + ' in ' + newtime + 'ms');
-        deferred.resolve();
-        // TODO clean up all the *.browserify files
-    });
-
-    outReleaseFileStream.on('error', function(err) {
-        var newtime = new Date().valueOf() - time;
-        events.emit('log', 'error while generating cordova.js');
-        deferred.reject();
-    });
-    return deferred.promise;
-}
-
-function computeCommitIdSync() {
-    var deferred = Q.defer();
-    computeCommitId(function(cId){
-        deferred.resolve(cId);
-    });
-    return deferred.promise;
-}
-
-function getPlatformVersion(cId, project_dir) {
-    var deferred = Q.defer();
-    //run version script for each platform to get platformVersion
-    var versionPath = path.join(project_dir, '/cordova/version');
-    childProcess.exec('"' + versionPath + '"', function(err, stdout, stderr) {
-        if (err) {
-            events.emit('log', 'Error running platform version script');
-            events.emit('log', err);
-            deferred.resolve('N/A');
-        } else {
-            deferred.resolve(stdout.trim());
-        }
-    });
-    return deferred.promise;
-}
-
-// Called on --prepare.
-// Sets up each plugin's Javascript code to be loaded properly.
-// Expects a path to the project (platforms/android in CLI, . in plugman-only),
-// a path to where the plugins are downloaded, the www dir, and the platform ('android', 'ios', etc.).
-module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_dir, is_top_level, pluginInfoProvider) {
-    // Process:
-    // - Do config munging by calling into config-changes module
-    // - List all plugins in plugins_dir
-    // - Load and parse their plugin.xml files.
-    // - Skip those without support for this platform. (No <platform> tags means JS-only!)
-    // - Build a list of all their js-modules, including platform-specific js-modules.
-    // - 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.
-    // This file is not really used. Maybe cordova app harness
-    events.emit('verbose', 'Preparing ' + platform + ' browserify project');
-    pluginInfoProvider = pluginInfoProvider || new PluginInfoProvider(); // Allow null for backwards-compat.
-    var platformJson = PlatformJson.load(plugins_dir, platform);
-    var wwwDir = www_dir || platform_modules.getPlatformProject(platform, project_dir).www_dir();
-
-    uninstallQueuedPlugins(platformJson, www_dir);
-
-    events.emit('verbose', 'Processing configuration changes for plugins.');
-    config_changes.process(plugins_dir, project_dir, platform, platformJson, pluginInfoProvider);
-
-    if(!is_top_level) {
-        return Q();
-    }
-
-    var commitId;
-    return computeCommitIdSync()
-    .then(function(cId){
-        commitId = cId;
-        return getPlatformVersion(commitId, project_dir);
-    }).then(function(platformVersion){
-        var libraryRelease = bundle(platform, false, commitId, platformVersion);
-
-        var pluginMetadata = {};
-        var modulesMetadata = [];
-
-        var plugins = Object.keys(platformJson.root.installed_plugins).concat(Object.keys(platformJson.root.dependent_plugins));
-        events.emit('verbose', 'Iterating over installed plugins:', plugins);
-        plugins && plugins.forEach(function(plugin) {
-            var pluginDir = path.join(plugins_dir, plugin);
-            var pluginInfo = pluginInfoProvider.get(pluginDir);
-            // pluginMetadata is a mapping from plugin IDs to versions.
-            pluginMetadata[pluginInfo.id] = pluginInfo.version;
-
-            // Copy www assets described in <asset> tags.
-            pluginInfo.getAssets(platform)
-            .forEach(function(asset) {
-                common.asset.install(asset, pluginDir, wwwDir);
-            });
-
-            pluginInfo.getJsModules(platform)
-            .forEach(function(jsModule) {
-                var moduleName = jsModule.name ? jsModule.name : path.basename(jsModule.src, '.js');
-                var moduleId = pluginInfo.id + '.' + moduleName;
-                var moduleMetadata = {file: jsModule.src, id: moduleId, name: moduleName};
-
-                if (jsModule.clobbers.length > 0) {
-                    moduleMetadata.clobbers = jsModule.clobbers.map(function(o) { return o.target; });
-                }
-                if (jsModule.merges.length > 0) {
-                    moduleMetadata.merges = jsModule.merges.map(function(o) { return o.target; });
-                }
-                if (jsModule.runs) {
-                    moduleMetadata.runs = true;
-                }
-
-                modulesMetadata.push(moduleMetadata);
-                libraryRelease.require(path.join(pluginDir, jsModule.src), { expose: moduleId });
-            });
-        });
-
-        events.emit('verbose', 'Writing out cordova_plugins.js...');
-
-        // Create a stream and write plugin metadata into it
-        // instead of generating intermediate file on FS
-        var cordova_plugins = new Readable();
-        cordova_plugins.push(
-            'module.exports.metadata = ' + JSON.stringify(pluginMetadata, null, 4) + ';\n' +
-            'module.exports = ' + JSON.stringify(modulesMetadata, null, 4) + ';\n', 'utf8');
-        cordova_plugins.push(null);
-
-        var bootstrap = new Readable();
-        bootstrap.push('require(\'cordova/init\');\n', 'utf8');
-        bootstrap.push(null);
-
-        var moduleAliases = modulesMetadata
-        .reduce(function (accum, meta) {
-            accum['./' + meta.name] = meta.id;
-            return accum;
-        }, {});
-
-        libraryRelease
-            .add(cordova_plugins, {file: path.join(wwwDir, 'cordova_plugins.js'), expose: 'cordova/plugin_list'})
-            .add(bootstrap)
-            .transform(aliasify, {aliases: moduleAliases});
-
-        var outReleaseFile = path.join(wwwDir, 'cordova.js');
-        return generateFinalBundle(platform, libraryRelease, outReleaseFile, commitId, platformVersion);
-    });
-};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/prepare.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/prepare.js b/cordova-lib/src/plugman/prepare.js
deleted file mode 100644
index c52d92e..0000000
--- a/cordova-lib/src/plugman/prepare.js
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-/* jshint expr:true, quotmark:false */
-
-var platform_modules = require('../platforms/platforms'),
-    path            = require('path'),
-    config_changes  = require('./util/config-changes'),
-    common          = require('./platforms/common'),
-    fs              = require('fs'),
-    shell           = require('shelljs'),
-    Q               = require('q'),
-    events          = require('../events');
-var PlatformJson = require('./util/PlatformJson');
-var PluginInfoProvider = require('../PluginInfoProvider');
-
-// Called on --prepare.
-// Sets up each plugin's Javascript code to be loaded properly.
-// Expects a path to the project (platforms/android in CLI, . in plugman-only),
-// a path to where the plugins are downloaded, the www dir, and the platform ('android', 'ios', etc.).
-module.exports = function handlePrepare(project_dir, platform, plugins_dir, www_dir, is_top_level, pluginInfoProvider) {
-    // Process:
-    // - Do config munging by calling into config-changes module
-    // - List all plugins in plugins_dir
-    // - Load and parse their plugin.xml files.
-    // - Skip those without support for this platform. (No <platform> tags means JS-only!)
-    // - Build a list of all their js-modules, including platform-specific js-modules.
-    // - 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.
-    events.emit('verbose', 'Preparing ' + platform + ' project');
-    pluginInfoProvider = pluginInfoProvider || new PluginInfoProvider(); // Allow null for backwards-compat.
-    var platformJson = PlatformJson.load(plugins_dir, platform);
-    var wwwDir = www_dir || platform_modules.getPlatformProject(platform, project_dir).www_dir();
-
-    // Check if there are any plugins queued for uninstallation, and if so, remove any of their plugin web assets loaded in
-    // via <js-module> elements
-    var plugins_to_uninstall = platformJson.root.prepare_queue.uninstalled;
-    if (plugins_to_uninstall && plugins_to_uninstall.length) {
-        var plugins_www = path.join(wwwDir, 'plugins');
-        if (fs.existsSync(plugins_www)) {
-            plugins_to_uninstall.forEach(function(plug) {
-                var id = plug.id;
-                var plugin_modules = path.join(plugins_www, id);
-                if (fs.existsSync(plugin_modules)) {
-                    events.emit('verbose', 'Removing plugins directory from www "'+plugin_modules+'"');
-                    shell.rm('-rf', plugin_modules);
-                }
-            });
-        }
-    }
-
-    events.emit('verbose', 'Processing configuration changes for plugins.');
-    config_changes.process(plugins_dir, project_dir, platform, platformJson, pluginInfoProvider);
-
-    // This array holds all the metadata for each module and ends up in cordova_plugins.json
-    var plugins = Object.keys(platformJson.root.installed_plugins).concat(Object.keys(platformJson.root.dependent_plugins));
-    var moduleObjects = [];
-    var pluginMetadata = {};
-    events.emit('verbose', 'Iterating over installed plugins:', plugins);
-
-    plugins && plugins.forEach(function(plugin) {
-        var pluginDir = path.join(plugins_dir, plugin);
-        var pluginInfo = pluginInfoProvider.get(pluginDir);
-
-        var plugin_id = pluginInfo.id;
-        // pluginMetadata is a mapping from plugin IDs to versions.
-        pluginMetadata[plugin_id] = pluginInfo.version;
-
-        // add the plugins dir to the platform's www.
-        var platformPluginsDir = path.join(wwwDir, 'plugins');
-        // XXX this should not be here if there are no js-module. It leaves an empty plugins/ directory
-        shell.mkdir('-p', platformPluginsDir);
-
-        var jsModules = pluginInfo.getJsModules(platform);
-        var assets = pluginInfo.getAssets(platform);
-
-        // Copy www assets described in <asset> tags.
-        assets.forEach(function(asset) {
-            common.asset.install(asset, pluginDir, wwwDir);
-        });
-
-        jsModules.forEach(function(module) {
-            // Copy the plugin's files into the www directory.
-            // NB: We can't always use path.* functions here, because they will use platform slashes.
-            // But the path in the plugin.xml and in the cordova_plugins.js should be always forward slashes.
-            var pathParts = module.src.split('/');
-
-            var fsDirname = path.join.apply(path, pathParts.slice(0, -1));
-            var fsDir = path.join(platformPluginsDir, plugin_id, fsDirname);
-            shell.mkdir('-p', fsDir);
-
-            // Read in the file, prepend the cordova.define, and write it back out.
-            var moduleName = plugin_id + '.';
-            if (module.name) {
-                moduleName += module.name;
-            } else {
-                var result = module.src.match(/([^\/]+)\.js/);
-                moduleName += result[1];
-            }
-
-            var fsPath = path.join.apply(path, pathParts);
-            var scriptContent = fs.readFileSync(path.join(pluginDir, fsPath), 'utf-8').replace(/^\ufeff/, ''); // Window BOM
-            if (fsPath.match(/.*\.json$/)) {
-                scriptContent = 'module.exports = ' + scriptContent;
-            }
-            scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) { ' + scriptContent + '\n});\n';
-            fs.writeFileSync(path.join(platformPluginsDir, plugin_id, fsPath), scriptContent, 'utf-8');
-
-            // Prepare the object for cordova_plugins.json.
-            var obj = {
-                file: ['plugins', plugin_id, module.src].join('/'),
-                id: moduleName
-            };
-            if (module.clobbers.length > 0) {
-                obj.clobbers = module.clobbers.map(function(o) { return o.target; });
-            }
-            if (module.merges.length > 0) {
-                obj.merges = module.merges.map(function(o) { return o.target; });
-            }
-            if (module.runs) {
-                obj.runs = true;
-            }
-
-            // Add it to the list of module objects bound for cordova_plugins.json
-            moduleObjects.push(obj);
-        });
-    });
-
-    // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js
-    var final_contents = "cordova.define('cordova/plugin_list', function(require, exports, module) {\n";
-    final_contents += 'module.exports = ' + JSON.stringify(moduleObjects,null,'    ') + ';\n';
-    final_contents += 'module.exports.metadata = \n';
-    final_contents += '// TOP OF METADATA\n';
-    final_contents += JSON.stringify(pluginMetadata, null, '    ') + '\n';
-    final_contents += '// BOTTOM OF METADATA\n';
-    final_contents += '});'; // Close cordova.define.
-
-    events.emit('verbose', 'Writing out cordova_plugins.js...');
-    fs.writeFileSync(path.join(wwwDir, 'cordova_plugins.js'), final_contents, 'utf-8');
-    
-    return Q();
-};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/uninstall.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/uninstall.js b/cordova-lib/src/plugman/uninstall.js
index 9fdc2e4..1bc1e2f 100644
--- a/cordova-lib/src/plugman/uninstall.js
+++ b/cordova-lib/src/plugman/uninstall.js
@@ -30,7 +30,6 @@ var path = require('path'),
     Q = require('q'),
     events = require('../events'),
     platform_modules = require('../platforms/platforms'),
-    plugman = require('./plugman'),
     promiseutil = require('../util/promise-util'),
     HooksRunner = require('../hooks/HooksRunner'),
     cordovaUtil      = require('../cordova/util');
@@ -299,49 +298,23 @@ function runUninstallPlatform(actions, platform, project_dir, plugin_dir, plugin
 
 // Returns a promise.
 function handleUninstall(actions, platform, pluginInfo, project_dir, www_dir, plugins_dir, is_top_level, options) {
-    var plugin_id = pluginInfo.id;
-    var plugin_dir = pluginInfo.dir;
-    var handler = platform_modules.getPlatformProject(platform, project_dir);
-    www_dir = www_dir || handler.www_dir();
-    events.emit('log', 'Uninstalling ' + plugin_id + ' from ' + platform);
-
-    var pluginItems = pluginInfo.getFilesAndFrameworks(platform);
-    var assets = pluginInfo.getAssets(platform);
-    var frameworkFiles = pluginInfo.getFrameworks(platform);
-
-    // queue up native stuff
-    pluginItems.forEach(function(item) {
-        // CB-5238 Don't uninstall non custom frameworks.
-        if (item.itemType == 'framework' && !item.custom) return;
-        actions.push(actions.createAction(handler.getUninstaller(item.itemType),
-                                          [item, plugin_id, options],
-                                          handler.getInstaller(item.itemType),
-                                          [item, plugin_dir, plugin_id, options]));
-    });
-
-    // queue up asset uninstallation
-    var common = require('./platforms/common');
-    assets.forEach(function(asset) {
-        actions.push(actions.createAction(common.asset.uninstall, [asset, www_dir, plugin_id], common.asset.install, [asset, plugin_dir, www_dir]));
-    });
+    events.emit('log', 'Uninstalling ' + pluginInfo.id + ' from ' + platform);
 
-    // run through the action stack
-    return actions.process(platform, project_dir)
+    // Set up platform to uninstall asset files/js modules
+    // from <platform>/platform_www dir instead of <platform>/www.
+    options.usePlatformWww = true;
+    return platform_modules.getPlatformApi(platform, project_dir)
+    .removePlugin(pluginInfo, options)
     .then(function() {
-        // WIN!
-        events.emit('verbose', plugin_id + ' uninstalled from ' + platform + '.');
-        // queue up the plugin so prepare can remove the config changes
-        var platformJson = PlatformJson.load(plugins_dir, platform);
-        platformJson.addUninstalledPluginToPrepareQueue(plugin_id, is_top_level);
-        platformJson.save();
-        // call prepare after a successful uninstall
-        if (options.browserify) {
-            return plugman.prepareBrowserify(project_dir, platform, plugins_dir, www_dir, is_top_level, options.pluginInfoProvider);
-        } else {
-            return plugman.prepare(project_dir, platform, plugins_dir, www_dir, is_top_level, options.pluginInfoProvider);
-        }
-    }).then(function() {
-        if (platform == 'android' && semver.gte(options.platformVersion, '4.0.0-dev') && frameworkFiles.length > 0) {
+        // Remove plugin from installed list. This already done in platform,
+        // but need to be duplicated here to remove plugin entry from project's
+        // plugin list to manage dependencies properly.
+        PlatformJson.load(plugins_dir, platform)
+            .removePlugin(pluginInfo.id, is_top_level)
+            .save();
+
+        if (platform == 'android' && semver.gte(options.platformVersion, '4.0.0-dev') &&
+                pluginInfo.getFrameworks(platform).length > 0) {
             events.emit('verbose', 'Updating build files since android plugin contained <framework>');
             var buildModule;
             try {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/util/ConfigFile.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/ConfigFile.js b/cordova-lib/src/plugman/util/ConfigFile.js
index c317668..15191ad 100644
--- a/cordova-lib/src/plugman/util/ConfigFile.js
+++ b/cordova-lib/src/plugman/util/ConfigFile.js
@@ -69,7 +69,7 @@ function ConfigFile_load() {
         self.data = xml_helpers.parseElementtreeSync(filepath);
     } else if (ext == '.pbxproj') {
         self.type = 'pbxproj';
-        var projectFile = platforms.getPlatformProject('ios', self.project_dir).parseProjectFile(self.project_dir);
+        var projectFile = platforms.getPlatformApi('ios', self.project_dir)._handler.parseProjectFile(self.project_dir);
         self.data = projectFile.xcode;
         self.cordovaVersion = projectFile.cordovaVersion;
     } else {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/util/PlatformJson.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/PlatformJson.js b/cordova-lib/src/plugman/util/PlatformJson.js
index 1b9f05b..318a5d9 100644
--- a/cordova-lib/src/plugman/util/PlatformJson.js
+++ b/cordova-lib/src/plugman/util/PlatformJson.js
@@ -81,6 +81,26 @@ PlatformJson.prototype.isPluginInstalled = function(pluginId) {
         this.isPluginDependent(pluginId);
 };
 
+PlatformJson.prototype.addPlugin = function(pluginId, variables, isTopLevel) {
+    var pluginsList = isTopLevel ?
+        this.root.installed_plugins :
+        this.root.dependent_plugins;
+
+    pluginsList[pluginId] = variables;
+
+    return this;
+};
+
+PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) {
+    var pluginsList = isTopLevel ?
+        this.root.installed_plugins :
+        this.root.dependent_plugins;
+
+    delete pluginsList[pluginId];
+
+    return this;
+};
+
 PlatformJson.prototype.addInstalledPluginToPrepareQueue = function(pluginDirName, vars, is_top_level) {
     this.root.prepare_queue.installed.push({'plugin':pluginDirName, 'vars':vars, 'topLevel':is_top_level});
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/util/action-stack.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/action-stack.js b/cordova-lib/src/plugman/util/action-stack.js
index c8bd21b..c1c2a4e 100644
--- a/cordova-lib/src/plugman/util/action-stack.js
+++ b/cordova-lib/src/plugman/util/action-stack.js
@@ -19,8 +19,7 @@
 
 /* jshint quotmark:false */
 
-var platforms = require("../../platforms/platforms"),
-    events = require('../../events'),
+var events = require('../../events'),
     Q = require('q');
 
 function ActionStack() {
@@ -47,22 +46,11 @@ ActionStack.prototype = {
     // Returns a promise.
     process:function(platform, project_dir) {
         events.emit('verbose', 'Beginning processing of action stack for ' + platform + ' project...');
-        var project_files;
-
-        // parse platform-specific project files once
-        var platformProject = platforms.getPlatformProject(platform, project_dir);
-        if (platformProject.parseProjectFile) {
-            events.emit('verbose', 'Parsing ' + platform + ' project files...');
-            project_files = platformProject.parseProjectFile(project_dir);
-        }
 
         while (this.stack.length) {
             var action = this.stack.shift();
             var handler = action.handler.run;
             var action_params = action.handler.params;
-            if (project_files) {
-                action_params.push(project_files);
-            }
 
             try {
                 handler.apply(null, action_params);
@@ -76,10 +64,6 @@ ActionStack.prototype = {
                     var revert = undo.reverter.run;
                     var revert_params = undo.reverter.params;
 
-                    if (project_files) {
-                        revert_params.push(project_files);
-                    }
-
                     try {
                         revert.apply(null, revert_params);
                     } catch(err) {
@@ -94,10 +78,6 @@ ActionStack.prototype = {
         }
         events.emit('verbose', 'Action stack processing complete.');
 
-        if (project_files) {
-            events.emit('verbose', 'Writing out ' + platform + ' project files...');
-            project_files.write();
-        }
         return Q();
     }
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/plugman/util/config-changes.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/util/config-changes.js b/cordova-lib/src/plugman/util/config-changes.js
index 63c3ec3..ff23e2a 100644
--- a/cordova-lib/src/plugman/util/config-changes.js
+++ b/cordova-lib/src/plugman/util/config-changes.js
@@ -54,8 +54,8 @@ var keep_these_frameworks = [
 exports.PlatformMunger = PlatformMunger;
 
 exports.process = function(plugins_dir, project_dir, platform, platformJson, pluginInfoProvider) {
-    var munger = new PlatformMunger(platform, project_dir, plugins_dir, platformJson, pluginInfoProvider);
-    munger.process();
+    var munger = new PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider);
+    munger.process(plugins_dir);
     munger.save_all();
 };
 
@@ -65,12 +65,10 @@ exports.process = function(plugins_dir, project_dir, platform, platformJson, plu
 * Can deal with config file of a single project.
 * Parsed config files are cached in a ConfigKeeper object.
 ******************************************************************************/
-function PlatformMunger(platform, project_dir, plugins_dir, platformJson, pluginInfoProvider) {
+function PlatformMunger(platform, project_dir, platformJson, pluginInfoProvider) {
     checkPlatform(platform);
     this.platform = platform;
     this.project_dir = project_dir;
-    this.plugins_dir = plugins_dir;
-    this.platform_handler = platforms.getPlatformProject(platform, project_dir);
     this.config_keeper = new ConfigKeeper(project_dir);
     this.platformJson = platformJson;
     this.pluginInfoProvider = pluginInfoProvider;
@@ -130,14 +128,15 @@ function PlatformMunger_apply_file_munge(file, munge, remove) {
 
 
 PlatformMunger.prototype.remove_plugin_changes = remove_plugin_changes;
-function remove_plugin_changes(plugin_name, plugin_id, is_top_level) {
+function remove_plugin_changes(pluginInfo, is_top_level) {
     var self = this;
     var platform_config = self.platformJson.root;
-    var plugin_dir = path.join(self.plugins_dir, plugin_name);
-    var plugin_vars = (is_top_level ? platform_config.installed_plugins[plugin_id] : platform_config.dependent_plugins[plugin_id]);
+    var plugin_vars = is_top_level ?
+        platform_config.installed_plugins[pluginInfo.id] :
+        platform_config.dependent_plugins[pluginInfo.id];
 
     // get config munge, aka how did this plugin change various config files
-    var config_munge = self.generate_plugin_config_munge(plugin_dir, plugin_vars);
+    var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars);
     // global munge looks at all plugins' changes to config files
     var global_munge = platform_config.config_munge;
     var munge = mungeutil.decrement_munge(global_munge, config_munge);
@@ -147,7 +146,7 @@ function remove_plugin_changes(plugin_name, plugin_id, is_top_level) {
             // TODO: remove this check and <plugins-plist> sections in spec/plugins/../plugin.xml files.
             events.emit(
                 'warn',
-                'WARNING: Plugin "' + plugin_id + '" uses <plugins-plist> element(s), ' +
+                'WARNING: Plugin "' + pluginInfo.id + '" uses <plugins-plist> element(s), ' +
                 'which are no longer supported. Support has been removed as of Cordova 3.4.'
             );
             continue;
@@ -168,22 +167,18 @@ function remove_plugin_changes(plugin_name, plugin_id, is_top_level) {
     }
 
     // Remove from installed_plugins
-    if (is_top_level) {
-        delete platform_config.installed_plugins[plugin_id];
-    } else {
-        delete platform_config.dependent_plugins[plugin_id];
-    }
+    self.platformJson.removePlugin(pluginInfo.id, is_top_level);
+    return self;
 }
 
 
 PlatformMunger.prototype.add_plugin_changes = add_plugin_changes;
-function add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increment) {
+function add_plugin_changes(pluginInfo, plugin_vars, is_top_level, should_increment) {
     var self = this;
     var platform_config = self.platformJson.root;
-    var plugin_dir = path.join(self.plugins_dir, plugin_id);
 
     // get config munge, aka how should this plugin change various config files
-    var config_munge = self.generate_plugin_config_munge(plugin_dir, plugin_vars);
+    var config_munge = self.generate_plugin_config_munge(pluginInfo, plugin_vars);
     // global munge looks at all plugins' changes to config files
 
     // TODO: The should_increment param is only used by cordova-cli and is going away soon.
@@ -203,7 +198,7 @@ function add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increme
         if (file == 'plugins-plist' && self.platform == 'ios') {
             events.emit(
                 'warn',
-                'WARNING: Plugin "' + plugin_id + '" uses <plugins-plist> element(s), ' +
+                'WARNING: Plugin "' + pluginInfo.id + '" uses <plugins-plist> element(s), ' +
                 'which are no longer supported. Support has been removed as of Cordova 3.4.'
             );
             continue;
@@ -222,12 +217,9 @@ function add_plugin_changes(plugin_id, plugin_vars, is_top_level, should_increme
         self.apply_file_munge(file, munge.files[file]);
     }
 
-    // Move to installed_plugins if it is a top-level plugin
-    if (is_top_level) {
-        platform_config.installed_plugins[plugin_id] = plugin_vars || {};
-    } else {
-        platform_config.dependent_plugins[plugin_id] = plugin_vars || {};
-    }
+    // Move to installed/dependent_plugins
+    self.platformJson.addPlugin(pluginInfo.id, plugin_vars || {}, is_top_level);
+    return self;
 }
 
 
@@ -253,24 +245,19 @@ function reapply_global_munge () {
 
         self.apply_file_munge(file, global_munge.files[file]);
     }
+
+    return self;
 }
 
 
 // generate_plugin_config_munge
 // Generate the munge object from plugin.xml + vars
 PlatformMunger.prototype.generate_plugin_config_munge = generate_plugin_config_munge;
-function generate_plugin_config_munge(plugin_dir, vars) {
+function generate_plugin_config_munge(pluginInfo, vars) {
     var self = this;
 
     vars = vars || {};
-    // Add PACKAGE_NAME variable into vars
-    if (!vars['PACKAGE_NAME']) {
-        vars['PACKAGE_NAME'] = self.platform_handler.package_name(self.project_dir);
-    }
-
     var munge = { files: {} };
-    var pluginInfo = self.pluginInfoProvider.get(plugin_dir);
-
     var changes = pluginInfo.getConfigFiles(self.platform);
 
     // note down pbxproj framework munges in special section of munge obj
@@ -283,10 +270,14 @@ function generate_plugin_config_munge(plugin_dir, vars) {
             }
         });
     }
-    
+
     // Demux 'package.appxmanifest' into relevant platform-specific appx manifests.
     // Only spend the cycles if there are version-specific plugin settings
-    if (self.platform === 'windows' && changes.some(function(change) { return ((typeof change.versions !== 'undefined') || (typeof change.deviceTarget !== 'undefined')); })) 
+    if (self.platform === 'windows' &&
+            changes.some(function(change) {
+                return ((typeof change.versions !== 'undefined') ||
+                    (typeof change.deviceTarget !== 'undefined'));
+            }))
     {
         var manifests = {
             'windows': {
@@ -334,7 +325,7 @@ function generate_plugin_config_munge(plugin_dir, vars) {
 
             // at this point, 'change' targets package.appxmanifest and has a version attribute
             knownWindowsVersionsForTargetDeviceSet.forEach(function(winver) {
-                // This is a local function that creates the new replacement representing the 
+                // This is a local function that creates the new replacement representing the
                 // mutation.  Used to save code further down.
                 var createReplacement = function(manifestFile, originalChange) {
                     var replacement = {
@@ -389,18 +380,20 @@ function generate_plugin_config_munge(plugin_dir, vars) {
 // Go over the prepare queue and apply the config munges for each plugin
 // that has been (un)installed.
 PlatformMunger.prototype.process = PlatformMunger_process;
-function PlatformMunger_process() {
+function PlatformMunger_process(plugins_dir) {
     var self = this;
     var platform_config = self.platformJson.root;
 
     // Uninstallation first
     platform_config.prepare_queue.uninstalled.forEach(function(u) {
-        self.remove_plugin_changes(u.plugin, u.id, u.topLevel);
+        var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin));
+        self.remove_plugin_changes(pluginInfo, u.topLevel);
     });
 
     // Now handle installation
     platform_config.prepare_queue.installed.forEach(function(u) {
-        self.add_plugin_changes(u.plugin, u.vars, u.topLevel, true);
+        var pluginInfo = self.pluginInfoProvider.get(path.join(plugins_dir, u.plugin));
+        self.add_plugin_changes(pluginInfo, u.vars, u.topLevel, true);
     });
 
     // Empty out installed/ uninstalled queues.


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[5/5] cordova-lib git commit: CB-9597 Initial Implementation of PlatformApiPoly

Posted by an...@apache.org.
CB-9597 Initial Implementation of PlatformApiPoly

This implements PlatformApiPoly class according to PlatformApi spec, which allows to:
  * create/update platform
  * execute platform's actions (build/run/add/update)
  * do a prepare (needed for CLI workflow only)
  * install/uninstall plugins

Other noticeable changes:
  * removes `getPlatformProject` and PlatformProject method/class in favor of PlatformApiPoly/getPlatformApi
  * make assets and js-modules installing/uninstalling through ActionStack
  * refactor configChanges to not require plugins_dir in constructor
  * moves mergeXml helper to xml-helpers

This should be used along with cordova-cli/platformApi branch


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

Branch: refs/heads/master
Commit: 07271a5c6162c0b2bee55e1ea23f91ebdfbbb34c
Parents: b0c1965
Author: Vladimir Kotikov <v-...@microsoft.com>
Authored: Thu Jul 9 16:55:31 2015 +0300
Committer: Vladimir Kotikov <v-...@microsoft.com>
Committed: Wed Sep 2 14:24:03 2015 +0300

----------------------------------------------------------------------
 cordova-lib/spec-cordova/cofdova-lib.spec.js    |  23 -
 cordova-lib/spec-cordova/compile.spec.js        |  36 +-
 cordova-lib/spec-cordova/cordova-lib.spec.js    |  23 +
 cordova-lib/spec-cordova/emulate.spec.js        |  48 +-
 .../platforms/android/AndroidManifest.xml       |  14 +
 .../platformApi/platforms/android/android.json  |  11 +
 .../platforms/android/res/xml/config.xml        |  17 +
 .../platforms/windows/cordova/Api.js            |   3 +
 .../metadata/android_parser.spec.js             |  12 +-
 .../metadata/blackberry_parser.spec.js          |  12 +-
 .../metadata/browser_parser.spec.js             |  10 +-
 .../metadata/firefoxos_parser.spec.js           |  10 +-
 .../spec-cordova/metadata/ios_parser.spec.js    |  12 +-
 .../spec-cordova/metadata/webos_parser.spec.js  |   6 +-
 .../metadata/windows8_parser.spec.js            |  12 +-
 .../spec-cordova/metadata/wp8_parser.spec.js    |  12 +-
 .../platforms/PlatformApiPoly.spec.js           | 315 +++++++++
 .../spec-cordova/platforms/platforms.spec.js    |  72 ++
 cordova-lib/spec-cordova/prepare.spec.js        | 159 +----
 cordova-lib/spec-cordova/run.spec.js            |  26 +-
 cordova-lib/spec-cordova/save.spec.js           |  31 +-
 cordova-lib/spec-cordova/xml-helpers.spec.js    | 132 ++++
 .../spec-plugman/install-browserify.spec.js     | 519 --------------
 cordova-lib/spec-plugman/install.spec.js        | 224 +++---
 cordova-lib/spec-plugman/prepare.spec.js        |  73 --
 .../spec-plugman/projects/wp8/config.xml        |  12 +
 .../spec-plugman/uninstall-browserify.spec.js   | 315 ---------
 cordova-lib/spec-plugman/uninstall.spec.js      | 117 ++-
 .../spec-plugman/util/action-stack.spec.js      |  14 +-
 .../spec-plugman/util/config-changes.spec.js    |  96 ++-
 cordova-lib/src/PluginInfo.js                   |   7 +-
 cordova-lib/src/cordova/clean.js                |  20 +-
 cordova-lib/src/cordova/compile.js              |  27 +-
 cordova-lib/src/cordova/emulate.js              |  18 +-
 cordova-lib/src/cordova/platform.js             | 161 ++---
 cordova-lib/src/cordova/plugin.js               | 107 ++-
 cordova-lib/src/cordova/prepare.js              | 205 +-----
 cordova-lib/src/cordova/requirements.js         |  42 +-
 cordova-lib/src/cordova/run.js                  |  17 +-
 cordova-lib/src/cordova/serve.js                |  19 +-
 cordova-lib/src/cordova/targets.js              |   7 +-
 cordova-lib/src/cordova/util.js                 |   9 +-
 cordova-lib/src/platforms/PlatformApiPoly.js    | 706 +++++++++++++++++++
 cordova-lib/src/platforms/platforms.js          | 107 +--
 cordova-lib/src/plugman/browserify.js           | 181 +++++
 cordova-lib/src/plugman/install.js              |  45 +-
 cordova-lib/src/plugman/platforms/common.js     |  30 +-
 cordova-lib/src/plugman/plugman.js              |   3 +-
 cordova-lib/src/plugman/prepare-browserify.js   | 214 ------
 cordova-lib/src/plugman/prepare.js              | 159 -----
 cordova-lib/src/plugman/uninstall.js            |  57 +-
 cordova-lib/src/plugman/util/ConfigFile.js      |   2 +-
 cordova-lib/src/plugman/util/PlatformJson.js    |  20 +
 cordova-lib/src/plugman/util/action-stack.js    |  22 +-
 cordova-lib/src/plugman/util/config-changes.js  |  71 +-
 cordova-lib/src/util/xml-helpers.js             |  72 ++
 56 files changed, 2268 insertions(+), 2426 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/cofdova-lib.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/cofdova-lib.spec.js b/cordova-lib/spec-cordova/cofdova-lib.spec.js
deleted file mode 100644
index 7f734b4..0000000
--- a/cordova-lib/spec-cordova/cofdova-lib.spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-/* jshint unused:false */
-
-// Verify that cordova-lib.js can be loaded
-var cordovaLib = require('../cordova-lib');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/compile.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/compile.spec.js b/cordova-lib/spec-cordova/compile.spec.js
index ea69743..67a8968 100644
--- a/cordova-lib/spec-cordova/compile.spec.js
+++ b/cordova-lib/spec-cordova/compile.spec.js
@@ -18,9 +18,7 @@
 */
 var cordova = require('../src/cordova/cordova'),
     platforms = require('../src/platforms/platforms'),
-    path = require('path'),
     HooksRunner = require('../src/hooks/HooksRunner'),
-    superspawn = require('../src/cordova/superspawn'),
     util = require('../src/cordova/util'),
     Q = require('q');
 
@@ -28,7 +26,7 @@ var supported_platforms = Object.keys(platforms).filter(function(p) { return p !
 
 
 describe('compile command', function() {
-    var is_cordova, list_platforms, fire, result, cd_project_root;
+    var is_cordova, list_platforms, fire, result, cd_project_root, fail, platformApi, getPlatformApi;
     var project_dir = '/some/path';
 
     function wrapper(f, post) {
@@ -43,7 +41,9 @@ describe('compile command', function() {
         cd_project_root = spyOn(util, 'cdProjectRoot').andReturn(project_dir);
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
         fire = spyOn(HooksRunner.prototype, 'fire').andReturn(Q());
-        spyOn(superspawn, 'spawn').andCallFake(function() { return Q(); });
+        platformApi = { build: jasmine.createSpy('build').andReturn(Q()) };
+        getPlatformApi = spyOn(platforms, 'getPlatformApi').andReturn(platformApi);
+        fail = function (err) { expect(err.stack).not.toBeDefined(); };
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
@@ -63,17 +63,21 @@ describe('compile command', function() {
     describe('success', function() {
         it('should run inside a Cordova-based project with at least one added platform and shell out to build', function(done) {
             cordova.raw.compile(['android','ios']).then(function() {
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'android', 'cordova', 'build'), [], jasmine.any(Object));
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'ios', 'cordova', 'build'), [], jasmine.any(Object));
-                done();
-            });
+                expect(getPlatformApi).toHaveBeenCalledWith('android');
+                expect(getPlatformApi).toHaveBeenCalledWith('ios');
+                expect(platformApi.build).toHaveBeenCalled();
+            })
+            .fail(fail)
+            .fin(done);
         });
 
         it('should pass down optional parameters', function (done) {
-            cordova.raw.compile({platforms:['blackberry10'], options:['--release']}).then(function () {
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'blackberry10', 'cordova', 'build'), ['--release'], jasmine.any(Object));
-                done();
-            });
+            cordova.raw.compile({platforms:['blackberry10'], options:{release: true}}).then(function () {
+                expect(getPlatformApi).toHaveBeenCalledWith('blackberry10');
+                expect(platformApi.build).toHaveBeenCalledWith({release: true});
+            })
+            .fail(fail)
+            .fin(done);
         });
     });
 
@@ -83,13 +87,17 @@ describe('compile command', function() {
                 cordova.raw.compile(['android', 'ios']).then(function() {
                     expect(fire).toHaveBeenCalledWith('before_compile', {verbose: false, platforms:['android', 'ios'], options: []});
                     done();
-                });
+                })
+                .fail(fail)
+                .fin(done);
             });
             it('should fire after hooks through the hooker module', function(done) {
                 cordova.raw.compile('android').then(function() {
                      expect(fire).toHaveBeenCalledWith('after_compile', {verbose: false, platforms:['android'], options: []});
                      done();
-                });
+                })
+                .fail(fail)
+                .fin(done);
             });
         });
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/cordova-lib.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/cordova-lib.spec.js b/cordova-lib/spec-cordova/cordova-lib.spec.js
new file mode 100644
index 0000000..7f734b4
--- /dev/null
+++ b/cordova-lib/spec-cordova/cordova-lib.spec.js
@@ -0,0 +1,23 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+/* jshint unused:false */
+
+// Verify that cordova-lib.js can be loaded
+var cordovaLib = require('../cordova-lib');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/emulate.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/emulate.spec.js b/cordova-lib/spec-cordova/emulate.spec.js
index 9561447..e38715f 100644
--- a/cordova-lib/spec-cordova/emulate.spec.js
+++ b/cordova-lib/spec-cordova/emulate.spec.js
@@ -18,8 +18,6 @@
 */
 var cordova = require('../src/cordova/cordova'),
     platforms = require('../src/platforms/platforms'),
-    superspawn = require('../src/cordova/superspawn'),
-    path = require('path'),
     HooksRunner = require('../src/hooks/HooksRunner'),
     Q = require('q'),
     util = require('../src/cordova/util');
@@ -27,9 +25,9 @@ var cordova = require('../src/cordova/cordova'),
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
 
 describe('emulate command', function() {
-    var is_cordova, cd_project_root, list_platforms, fire, result;
+    var is_cordova, cd_project_root, list_platforms, fire, result, fail;
     var project_dir = '/some/path';
-    var prepare_spy;
+    var prepare_spy, platformApi, getPlatformApi;
 
     function wrapper(f, post) {
         runs(function() {
@@ -45,7 +43,9 @@ describe('emulate command', function() {
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
         fire = spyOn(HooksRunner.prototype, 'fire').andReturn(Q());
         prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
-        spyOn(superspawn, 'spawn').andCallFake(Q);
+        fail = function (err) { expect(err.stack).not.toBeDefined(); };
+        platformApi = { run: jasmine.createSpy('run').andReturn(Q()) };
+        getPlatformApi = spyOn(platforms, 'getPlatformApi').andReturn(platformApi);
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function() {
@@ -66,19 +66,21 @@ describe('emulate command', function() {
         it('should run inside a Cordova-based project with at least one added platform and call prepare and shell out to the emulate script', function(done) {
             cordova.raw.emulate(['android','ios']).then(function(err) {
                 expect(prepare_spy).toHaveBeenCalledWith(['android', 'ios']);
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'android', 'cordova', 'run'), ['--emulator'], jasmine.any(Object));
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'ios', 'cordova', 'run'), ['--emulator'], jasmine.any(Object));
-
-                done();
-            });
+                expect(getPlatformApi).toHaveBeenCalledWith('android');
+                expect(getPlatformApi).toHaveBeenCalledWith('ios');
+                expect(platformApi.run).toHaveBeenCalled();
+            })
+            .fail(fail)
+            .fin(done);
         });
         it('should pass down options', function(done) {
-            cordova.raw.emulate({platforms: ['ios'], options:['--optionTastic']}).then(function(err) {
+            cordova.raw.emulate({platforms: ['ios'], options: {optionTastic: true }}).then(function(err) {
                 expect(prepare_spy).toHaveBeenCalledWith(['ios']);
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'ios', 'cordova', 'run'), ['--emulator', '--optionTastic'], jasmine.any(Object));
-
-                done();
-            });
+                expect(getPlatformApi).toHaveBeenCalledWith('ios');
+                expect(platformApi.run).toHaveBeenCalledWith({ device: false, emulator: true, optionTastic: true });
+            })
+            .fail(fail)
+            .fin(done);
         });
     });
 
@@ -86,15 +88,19 @@ describe('emulate command', function() {
         describe('when platforms are added', function() {
             it('should fire before hooks through the hooker module', function(done) {
                 cordova.raw.emulate(['android', 'ios']).then(function() {
-                    expect(fire).toHaveBeenCalledWith('before_emulate', {verbose: false, platforms:['android', 'ios'], options: []});
-                    done();
-                });
+                    expect(fire).toHaveBeenCalledWith('before_emulate',
+                        jasmine.objectContaining({verbose: false, platforms:['android', 'ios'], options: jasmine.any(Object)}));
+                })
+                .fail(fail)
+                .fin(done);
             });
             it('should fire after hooks through the hooker module', function(done) {
                 cordova.raw.emulate('android').then(function() {
-                     expect(fire).toHaveBeenCalledWith('after_emulate', {verbose: false, platforms:['android'], options: []});
-                     done();
-                });
+                     expect(fire).toHaveBeenCalledWith('after_emulate',
+                        jasmine.objectContaining({verbose: false, platforms:['android'], options: jasmine.any(Object)}));
+                })
+                .fail(fail)
+                .fin(done);
             });
         });
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/AndroidManifest.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/AndroidManifest.xml b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/AndroidManifest.xml
new file mode 100644
index 0000000..be3f245
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/AndroidManifest.xml
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='utf-8'?>
+<manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" android:windowSoftInputMode="adjustPan" package="org.testing" xmlns:android="http://schemas.android.com/apk/res/android">
+    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <application android:debuggable="true" android:hardwareAccelerated="true" android:icon="@drawable/icon" android:label="@string/app_name">
+        <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/app_name" android:name="TestBase" android:theme="@android:style/Theme.Black.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17" />
+</manifest>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/android.json
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/android.json b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/android.json
new file mode 100644
index 0000000..07c3697
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/android.json
@@ -0,0 +1,11 @@
+{
+    "prepare_queue": {
+        "installed": [],
+        "uninstalled": []
+    },
+    "config_munge": {
+        "files": {}
+    },
+    "installed_plugins": {},
+    "dependent_plugins": {}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/res/xml/config.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/res/xml/config.xml b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/res/xml/config.xml
new file mode 100644
index 0000000..645eeb5
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/android/res/xml/config.xml
@@ -0,0 +1,17 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget id="io.cordova.hellocordova" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+    <name>Hello Cordova</name>
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+    <author email="dev@cordova.apache.org" href="http://cordova.io">
+        Apache Cordova Team
+    </author>
+    <content src="index.html" />
+    <access origin="*" />
+    <preference name="fullscreen" value="true" />
+    <preference name="webviewbounce" value="true" />
+</widget>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
new file mode 100644
index 0000000..bb56cda
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/projects/platformApi/platforms/windows/cordova/Api.js
@@ -0,0 +1,3 @@
+module.exports = function PlatformApi (argument) {
+    this.platform = 'windows';
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/android_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/android_parser.spec.js b/cordova-lib/spec-cordova/metadata/android_parser.spec.js
index fd29806..1f56628 100644
--- a/cordova-lib/spec-cordova/metadata/android_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/android_parser.spec.js
@@ -19,7 +19,7 @@
 
 /* jshint boss:true */
 
-var platforms = require('../../src/platforms/platforms'),
+var androidParser = require('../../src/cordova/metadata/android_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -65,12 +65,12 @@ describe('android project parser', function() {
         it('should throw if provided directory does not contain an AndroidManifest.xml', function() {
             exists.andReturn(false);
             expect(function() {
-                new platforms.android.parser(android_proj);
+                new androidParser(android_proj);
             }).toThrow();
         });
         it('should create an instance with path, strings, manifest and android_config properties', function() {
             expect(function() {
-                var p = new platforms.android.parser(android_proj);
+                var p = new androidParser(android_proj);
                 expect(p.path).toEqual(android_proj);
                 expect(p.strings).toEqual(path.join(android_proj, 'res', 'values', 'strings.xml'));
                 expect(p.manifest).toEqual(path.join(android_proj, 'AndroidManifest.xml'));
@@ -78,11 +78,11 @@ describe('android project parser', function() {
             }).not.toThrow();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.android.parser(android_proj) instanceof Parser).toBe(true);
+            expect(new androidParser(android_proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.android.parser(android_proj);
+            var p = new androidParser(android_proj);
             expect(call).toHaveBeenCalledWith(p, 'android', android_proj);
         });
     });
@@ -94,7 +94,7 @@ describe('android project parser', function() {
         beforeEach(function() {
             stringsRoot = null;
             manifestRoot = null;
-            p = new platforms.android.parser(android_proj);
+            p = new androidParser(android_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             is_cordova = spyOn(util, 'isCordova').andReturn(android_proj);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/blackberry_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/blackberry_parser.spec.js b/cordova-lib/spec-cordova/metadata/blackberry_parser.spec.js
index aea0a49..7df3e24 100644
--- a/cordova-lib/spec-cordova/metadata/blackberry_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/blackberry_parser.spec.js
@@ -17,7 +17,7 @@
     under the License.
 */
 
-var platforms = require('../../src/platforms/platforms'),
+var blackberryParser = require('../../src/cordova/metadata/blackberry10_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -76,22 +76,22 @@ describe('blackberry10 project parser', function() {
         it('should throw an exception with a path that is not a native blackberry project', function() {
             exists.andReturn(false);
             expect(function() {
-                new platforms.blackberry10.parser(proj);
+                new blackberryParser(proj);
             }).toThrow();
         });
         it('should accept a proper native blackberry project path as construction parameter', function() {
             var project;
             expect(function() {
-                project = new platforms.blackberry10.parser(proj);
+                project = new blackberryParser(proj);
             }).not.toThrow();
             expect(project).toBeDefined();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.blackberry10.parser(proj) instanceof Parser).toBe(true);
+            expect(new blackberryParser(proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.blackberry10.parser(proj);
+            var p = new blackberryParser(proj);
             expect(call).toHaveBeenCalledWith(p, 'blackberry10', proj);
         });
     });
@@ -100,7 +100,7 @@ describe('blackberry10 project parser', function() {
         var p, cp, rm, mkdir, is_cordova, write, read;
         var bb_proj = path.join(proj, 'platforms', 'blackberry10');
         beforeEach(function() {
-            p = new platforms.blackberry10.parser(bb_proj);
+            p = new blackberryParser(bb_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             mkdir = spyOn(shell, 'mkdir');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/browser_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/browser_parser.spec.js b/cordova-lib/spec-cordova/metadata/browser_parser.spec.js
index 2677103..a44d7d3 100644
--- a/cordova-lib/spec-cordova/metadata/browser_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/browser_parser.spec.js
@@ -17,7 +17,7 @@
     under the License.
 */
 
-var platforms = require('../../src/platforms/platforms'),
+var browserParser = require('../../src/cordova/metadata/browser_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -35,16 +35,16 @@ describe('browser project parser', function() {
     describe('constructions', function() {
         it('should create an instance with a path', function() {
             expect(function() {
-                var p = new platforms.browser.parser(proj);
+                var p = new browserParser(proj);
                 expect(p.path).toEqual(proj);
             }).not.toThrow();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.browser.parser(proj) instanceof Parser).toBe(true);
+            expect(new browserParser(proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.browser.parser(proj);
+            var p = new browserParser(proj);
             expect(call).toHaveBeenCalledWith(p, 'browser', proj);
         });
     });
@@ -54,7 +54,7 @@ describe('browser project parser', function() {
         var browser_proj = path.join(proj, 'platforms', 'browser');
 
         beforeEach(function() {
-            p = new platforms.browser.parser(browser_proj);
+            p = new browserParser(browser_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             mkdir = spyOn(shell, 'mkdir');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/firefoxos_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/firefoxos_parser.spec.js b/cordova-lib/spec-cordova/metadata/firefoxos_parser.spec.js
index c918d16..e4a7eaa 100644
--- a/cordova-lib/spec-cordova/metadata/firefoxos_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/firefoxos_parser.spec.js
@@ -19,7 +19,7 @@
 
 /* jshint boss:true */
 
-var platforms = require('../../src/platforms/platforms'),
+var firefoxosParser = require('../../src/cordova/metadata/firefoxos_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -53,18 +53,18 @@ describe('firefoxos project parser', function() {
     describe('constructions', function() {
         it('should create an instance with a path', function() {
             expect(function() {
-                var p = new platforms.firefoxos.parser(proj);
+                var p = new firefoxosParser(proj);
                 expect(p.path).toEqual(proj);
                 expect(p.config_path).toEqual(path.join(proj, 'config.xml'));
                 expect(p.manifest_path).toEqual(path.join(p.www_dir(), 'manifest.webapp'));
             }).not.toThrow();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.firefoxos.parser(proj) instanceof Parser).toBe(true);
+            expect(new firefoxosParser(proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.firefoxos.parser(proj);
+            var p = new firefoxosParser(proj);
             expect(call).toHaveBeenCalledWith(p, 'firefoxos', proj);
         });
     });
@@ -74,7 +74,7 @@ describe('firefoxos project parser', function() {
         var ff_proj = path.join(proj, 'platforms', 'firefoxos');
         var manifestJson = null;
         beforeEach(function() {
-            p = new platforms.firefoxos.parser(ff_proj);
+            p = new firefoxosParser(ff_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             is_cordova = spyOn(util, 'isCordova').andReturn(proj);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/ios_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/ios_parser.spec.js b/cordova-lib/spec-cordova/metadata/ios_parser.spec.js
index 6ab42da..d7ae6f0 100644
--- a/cordova-lib/spec-cordova/metadata/ios_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/ios_parser.spec.js
@@ -16,7 +16,7 @@
  specific language governing permissions and limitations
  under the License.
  */
-var platforms = require('../../src/platforms/platforms'),
+var iosParser = require('../../src/cordova/metadata/ios_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -55,23 +55,23 @@ describe('ios project parser', function () {
         it('should throw if provided directory does not contain an xcodeproj file', function() {
             readdir.andReturn(['noxcodehere']);
             expect(function() {
-                new platforms.ios.parser(proj);
+                new iosParser(proj);
             }).toThrow();
         });
         it('should create an instance with path, pbxproj, xcodeproj, originalName and cordovaproj properties', function() {
             expect(function() {
-                var p = new platforms.ios.parser(proj);
+                var p = new iosParser(proj);
                 expect(p.path).toEqual(proj);
                 expect(p.pbxproj).toEqual(path.join(proj, 'test.xcodeproj', 'project.pbxproj'));
                 expect(p.xcodeproj).toEqual(path.join(proj, 'test.xcodeproj'));
             }).not.toThrow();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.ios.parser(proj) instanceof Parser).toBe(true);
+            expect(new iosParser(proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.ios.parser(proj);
+            var p = new iosParser(proj);
             expect(call).toHaveBeenCalledWith(p, 'ios', proj);
         });
     });
@@ -80,7 +80,7 @@ describe('ios project parser', function () {
         var p, cp, rm, mkdir, is_cordova, write, read, getOrientation;
         var ios_proj = path.join(proj, 'platforms', 'ios');
         beforeEach(function() {
-            p = new platforms.ios.parser(ios_proj);
+            p = new iosParser(ios_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             mkdir = spyOn(shell, 'mkdir');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/webos_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/webos_parser.spec.js b/cordova-lib/spec-cordova/metadata/webos_parser.spec.js
index 59010c4..c8b1b9b 100755
--- a/cordova-lib/spec-cordova/metadata/webos_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/webos_parser.spec.js
@@ -16,7 +16,7 @@
     specific language governing permissions and limitations
     under the License.
 */
-var platforms = require('../../src/platforms/platforms'),
+var webosParser = require('../../src/cordova/metadata/webos_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -39,7 +39,7 @@ describe('webos project parser', function() {
     describe('constructions', function() {
         it('should create an instance with a path', function() {
             expect(function() {
-                var p = new platforms.android.parser(proj);
+                var p = new webosParser(proj);
                 expect(p.path).toEqual(proj);
             }).not.toThrow();
         });
@@ -49,7 +49,7 @@ describe('webos project parser', function() {
         var p, cp, rm, is_cordova, write, read;
         var wos_proj = path.join(proj, 'platforms', 'webos');
         beforeEach(function() {
-            p = new platforms.webos.parser(wos_proj);
+            p = new webosParser(wos_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             is_cordova = spyOn(util, 'isCordova').andReturn(proj);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/windows8_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/windows8_parser.spec.js b/cordova-lib/spec-cordova/metadata/windows8_parser.spec.js
index f8371d5..d26b5b3 100644
--- a/cordova-lib/spec-cordova/metadata/windows8_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/windows8_parser.spec.js
@@ -19,7 +19,7 @@
 
 /* jshint boss:true */
 
-var platforms = require('../../src/platforms/platforms'),
+var windowsParser = require('../../src/cordova/metadata/windows_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -81,22 +81,22 @@ describe('windows8 project parser', function() {
         it('should throw if provided directory does not contain a jsproj file', function() {
             readdir.andReturn([]);
             expect(function() {
-                new platforms.windows8.parser(proj);
+                new windowsParser(proj);
             }).toThrow();
         });
         it('should create an instance with path, manifest properties', function() {
             expect(function() {
-                var parser = new platforms.windows8.parser(proj);
+                var parser = new windowsParser(proj);
                 expect(parser.projDir).toEqual(proj);
                 expect(parser.manifestPath).toEqual(path.join(proj, 'package.appxmanifest'));
             }).not.toThrow();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.windows8.parser(proj) instanceof Parser).toBe(true);
+            expect(new windowsParser(proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.windows8.parser(proj);
+            var p = new windowsParser(proj);
             expect(call).toHaveBeenCalledWith(p, 'windows8', proj);
         });
     });
@@ -105,7 +105,7 @@ describe('windows8 project parser', function() {
         var parser, cp, rm, is_cordova, write, read, mv, mkdir;
         var windows8_proj = path.join(proj, 'platforms', 'windows8');
         beforeEach(function() {
-            parser = new platforms.windows8.parser(windows8_proj);
+            parser = new windowsParser(windows8_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             mv = spyOn(shell, 'mv');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/metadata/wp8_parser.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/metadata/wp8_parser.spec.js b/cordova-lib/spec-cordova/metadata/wp8_parser.spec.js
index 5228fa7..aae2643 100644
--- a/cordova-lib/spec-cordova/metadata/wp8_parser.spec.js
+++ b/cordova-lib/spec-cordova/metadata/wp8_parser.spec.js
@@ -19,7 +19,7 @@
 
 /* jshint boss:true, sub:true */
 
-var platforms = require('../../src/platforms/platforms'),
+var wp8Parser = require('../../src/cordova/metadata/wp8_parser'),
     util = require('../../src/cordova/util'),
     path = require('path'),
     shell = require('shelljs'),
@@ -105,22 +105,22 @@ describe('wp8 project parser', function() {
         it('should throw if provided directory does not contain a csproj file', function() {
             readdir.andReturn([]);
             expect(function() {
-                new platforms.wp8.parser(proj);
+                new wp8Parser(proj);
             }).toThrow();
         });
         it('should create an instance with path, manifest properties', function() {
             expect(function() {
-                var p = new platforms.wp8.parser(proj);
+                var p = new wp8Parser(proj);
                 expect(p.wp8_proj_dir).toEqual(proj);
                 expect(p.manifest_path).toEqual(path.join(proj, 'Properties', 'WMAppManifest.xml'));
             }).not.toThrow();
         });
         it('should be an instance of Parser', function() {
-            expect(new platforms.wp8.parser(proj) instanceof Parser).toBe(true);
+            expect(new wp8Parser(proj) instanceof Parser).toBe(true);
         });
         it('should call super with the correct arguments', function() {
             var call = spyOn(Parser, 'call');
-            var p = new platforms.wp8.parser(proj);
+            var p = new wp8Parser(proj);
             expect(call).toHaveBeenCalledWith(p, 'wp8', proj);
         });
     });
@@ -129,7 +129,7 @@ describe('wp8 project parser', function() {
         var p, cp, rm, is_cordova, write, read, mv, mkdir, getOrientation;
         var wp8_proj = path.join(proj, 'platforms', 'wp8');
         beforeEach(function() {
-            p = new platforms.wp8.parser(wp8_proj);
+            p = new wp8Parser(wp8_proj);
             cp = spyOn(shell, 'cp');
             rm = spyOn(shell, 'rm');
             mv = spyOn(shell, 'mv');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/platforms/PlatformApiPoly.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/platforms/PlatformApiPoly.spec.js b/cordova-lib/spec-cordova/platforms/PlatformApiPoly.spec.js
new file mode 100644
index 0000000..0f60dd8
--- /dev/null
+++ b/cordova-lib/spec-cordova/platforms/PlatformApiPoly.spec.js
@@ -0,0 +1,315 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var et = require('elementtree');
+var path = require('path');
+var shell = require('shelljs');
+var xmlHelpers = require('../../src/util/xml-helpers');
+var ActionStack = require('../../src/plugman/util/action-stack');
+var superspawn = require('../../src/cordova/superspawn');
+var PluginInfo = require('../../src/PluginInfo');
+var ConfigParser = require('../../src/configparser/ConfigParser');
+var knownPlatforms = require('../../src/platforms/platforms');
+var PlatformApiPoly = require('../../src/platforms/PlatformApiPoly');
+
+var PLATFORM = 'android';
+var PLATFORM_VERSION = '3.7.0';
+var PLATFORM_LIB = '/some/platform/lib';
+var CORDOVA_ROOT = path.join(__dirname, '../fixtures/projects/platformApi');
+var PLATFORM_ROOT = path.join(CORDOVA_ROOT, 'platforms/android');
+var DUMMY_PLUGIN = path.join(__dirname, '../fixtures/plugins/test');
+var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
+    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
+    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
+    '        id        = "io.cordova.hellocordova"\n' +
+    '        version   = "0.0.1">\n' +
+    '    <name>Hello Cordova</name>\n' +
+    '    <description>\n' +
+    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
+    '    </description>\n' +
+    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
+    '        Apache Cordova Team\n' +
+    '    </author>\n' +
+    '    <content src="index.html" />\n' +
+    '    <access origin="*" />\n' +
+    '    <preference name="fullscreen" value="true" />\n' +
+    '    <preference name="webviewbounce" value="true" />\n' +
+    '</widget>\n';
+
+var platformApiPolyPublicMethods = [
+    'getPlatformInfo',
+    'prepare',
+    'addPlugin',
+    'removePlugin',
+    'updatePlugin',
+    'build',
+    'run',
+    'clean',
+    'requirements'
+];
+
+describe('PlatformApi polyfill', function () {
+    var platformApi;
+
+    beforeEach(function () {
+        var originalParseElementtreeSync = xmlHelpers.parseElementtreeSync;
+        spyOn(xmlHelpers, 'parseElementtreeSync').andCallFake(function (configPath) {
+            return /config\.xml$/.test(configPath) ? new et.ElementTree(et.XML(TEST_XML)) :
+                originalParseElementtreeSync(configPath);
+        });
+
+        platformApi = new PlatformApiPoly(PLATFORM, PLATFORM_ROOT);
+    });
+
+    it('should be constructable', function () {
+        var api;
+        expect(function(){api = new PlatformApiPoly(PLATFORM, PLATFORM_ROOT);}).not.toThrow();
+        expect(api).toEqual(jasmine.any(PlatformApiPoly));
+    });
+
+    it('should fail when unknown platform is specified', function () {
+        var api;
+        expect(function(){api = new PlatformApiPoly('fakePlatform', PLATFORM_ROOT);}).toThrow();
+    });
+
+    it('should fail when mandatory argument is not specified', function () {
+        var api;
+        expect(function(){api = new PlatformApiPoly(PLATFORM);}).toThrow();
+        expect(function(){api = new PlatformApiPoly(null, PLATFORM_ROOT);}).toThrow();
+    });
+
+    it('should have fields defined', function () {
+        expect(platformApi.platform).toBe(PLATFORM);
+        expect(platformApi.root).toBe(PLATFORM_ROOT);
+    });
+
+    it('should have \'static\' methods defined', function () {
+        expect(platformApi.constructor.createPlatform).toEqual(jasmine.any(Function));
+        expect(platformApi.constructor.updatePlatform).toEqual(jasmine.any(Function));
+    });
+
+    it('should have methods defined', function () {
+        platformApiPolyPublicMethods.forEach(function (methodName) {
+            expect(platformApi[methodName]).toEqual(jasmine.any(Function));
+        });
+    });
+
+    describe('methods:', function () {
+
+        var FAKE_PROJECT, OPTIONS, getPlatformApi, fail, success;
+
+        beforeEach(function () {
+            getPlatformApi = spyOn(knownPlatforms, 'getPlatformApi').andReturn(platformApi);
+
+            spyOn(shell, 'cp');
+            spyOn(shell, 'rm');
+            spyOn(shell, 'mkdir');
+            spyOn(fs, 'writeFileSync');
+
+            fail = jasmine.createSpy('fail');
+            success = jasmine.createSpy('success');
+
+            FAKE_PROJECT = {locations: {platforms: path.dirname(PLATFORM_ROOT), www: path.join(CORDOVA_ROOT, 'www')}, projectConfig: new ConfigParser('/fake/config.xml')};
+            OPTIONS = {platformDetails: {libDir: PLATFORM_LIB, platform: PLATFORM, version: PLATFORM_VERSION}};
+        });
+
+        describe('static create/updatePlatform methods', function () {
+            var spawn;
+
+            beforeEach(function () {
+                spawn = spyOn(superspawn, 'spawn').andReturn(Q());
+            });
+
+            it('should create/update platform through running platforms\' scripts', function (done) {
+                Q.all([PlatformApiPoly.createPlatform(FAKE_PROJECT, OPTIONS),
+                       PlatformApiPoly.updatePlatform(FAKE_PROJECT, OPTIONS)])
+                .then(function () {
+                    expect(spawn).toHaveBeenCalled();
+                    expect(spawn.calls.length).toBe(2);
+                }).fail(function (err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
+            });
+
+            it('should pass down arguments to platforms\' scripts', function (done) {
+                Q.all([PlatformApiPoly.createPlatform(FAKE_PROJECT, OPTIONS),
+                       PlatformApiPoly.updatePlatform(FAKE_PROJECT, OPTIONS)])
+                .then(function () {
+                    expect(spawn).toHaveBeenCalled();
+                    expect(spawn.calls.length).toBe(2);
+                    expect(spawn.calls[0].args[0]).toBe(path.join(PLATFORM_LIB, 'bin/create'));
+                    expect(spawn.calls[0].args[1]).toContain(PLATFORM_ROOT);
+                    expect(spawn.calls[1].args[0]).toBe(path.join(PLATFORM_LIB, 'bin/update'));
+                    expect(spawn.calls[1].args[1]).toContain(PLATFORM_ROOT);
+                }).fail(function (err) {
+                    expect(err).not.toBeDefined();
+                }).fin(done);
+            });
+
+            it('should copy cordova JS sources into created platform', function (done) {
+                Q.all([PlatformApiPoly.createPlatform(FAKE_PROJECT, OPTIONS),
+                       PlatformApiPoly.updatePlatform(FAKE_PROJECT, OPTIONS)])
+                .then(function () {
+                    expect(shell.cp).toHaveBeenCalled();
+                    expect(shell.cp.calls.length).toBe(2);
+                }).fail(fail)
+                .fin(function () {
+                    expect(fail).not.toHaveBeenCalled();
+                    done();
+                });
+            });
+
+            it('should fail immediately if options.platformInfo is not specified', function (done) {
+                Q.all([PlatformApiPoly.createPlatform(FAKE_PROJECT),
+                       PlatformApiPoly.updatePlatform(FAKE_PROJECT)])
+                .then(success)
+                .fail(fail)
+                .fin(function function_name (argument) {
+                    expect(success).not.toHaveBeenCalled();
+                    expect(fail).toHaveBeenCalled();
+                    expect(spawn).not.toHaveBeenCalled();
+                    done();
+                });
+            });
+        });
+
+        describe('prepare method', function () {
+            beforeEach(function () {
+                spyOn(platformApi._parser, 'update_www');
+                spyOn(platformApi._parser, 'update_project').andReturn(Q());
+            });
+
+            it('should return promise', function (done) {
+                var promise = platformApi.prepare(FAKE_PROJECT, OPTIONS);
+                expect(Q.isPromise(promise)).toBeTruthy();
+                promise.fin(done);
+            });
+
+            it('should call parser\'s corresponding methods', function (done) {
+                platformApi.prepare(FAKE_PROJECT, OPTIONS)
+                .then(function () {
+                    [platformApi._parser.update_www, platformApi._parser.update_project]
+                    .forEach(function (method) {
+                        expect(method).toHaveBeenCalled();
+                    });
+                })
+                .fail(fail)
+                .fin(function () {
+                    expect(fail).not.toHaveBeenCalled();
+                    done();
+                });
+            });
+        });
+
+        describe('pluginAdd method', function () {
+            var plugin, actionsProcess;
+
+            beforeEach(function () {
+                plugin = new PluginInfo(DUMMY_PLUGIN);
+                actionsProcess = spyOn(ActionStack.prototype, 'process').andCallThrough();
+            });
+
+            it('should return promise', function (done) {
+                var promise = platformApi.addPlugin(plugin);
+                expect(Q.isPromise(promise)).toBeTruthy();
+                promise.fin(done);
+            });
+
+            it('should fail if plugin parameter is not specified', function (done) {
+                platformApi.addPlugin()
+                .then(success)
+                .fail(fail)
+                .fin(function () {
+                    expect(success).not.toHaveBeenCalled();
+                    expect(fail).toHaveBeenCalled();
+                    done();
+                });
+            });
+
+            it('should process all plugin files through action stack', function (done) {
+                platformApi.addPlugin(plugin)
+                .then(success)
+                .fail(fail)
+                .fin(function () {
+                    expect(actionsProcess).toHaveBeenCalled();
+                    expect(success).toHaveBeenCalled();
+                    expect(fail).not.toHaveBeenCalled();
+                    done();
+                });
+            });
+        });
+
+        describe('platform actions', function () {
+            var spawnSpy;
+
+            beforeEach(function () {
+                spawnSpy = spyOn(superspawn, 'spawn');
+            });
+
+            it('should return promise', function (done) {
+                var ops = [
+                    platformApi.build(/*opts*/),
+                    platformApi.run(/*opts*/),
+                    platformApi.clean(/*opts*/),
+                    platformApi.requirements()
+                ];
+
+                ops.forEach(function (op) {
+                    expect(Q.isPromise(op));
+                });
+                Q.all(ops).fin(done);
+            });
+
+            it('should do their job through running platforms\' scripts', function (done) {
+                var ops = [
+                    platformApi.build(/*opts*/),
+                    platformApi.run(/*opts*/),
+                    platformApi.clean(/*opts*/)
+                ];
+
+                Q.all(ops)
+                .then(function () {
+                    expect(spawnSpy).toHaveBeenCalled();
+                    expect(spawnSpy.calls.length).toEqual(3);
+                }).fin(done);
+            });
+
+            it('should convert and pass down options to platforms\' scripts', function (done) {
+                var options = {
+                    release: true,
+                    nobuild: true,
+                    device: true,
+                    target: 'FakeDevice',
+                    archs: ['arm', 'x86'],
+                    buildConfig: '/some/path'
+                };
+                spawnSpy.andReturn(Q());
+                platformApi.build(options)
+                .then(function () {
+                    ['--release', '--nobuild', '--device', '--target=' + options.target, '--archs=arm,x86', '--buildConfig='  +options.buildConfig]
+                    .forEach(function (arg) {
+                        expect(spawnSpy.calls[0].args[1]).toContain(arg);
+                    });
+                }).fin(done);
+            });
+        });
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/platforms/platforms.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/platforms/platforms.spec.js b/cordova-lib/spec-cordova/platforms/platforms.spec.js
new file mode 100644
index 0000000..90b690c
--- /dev/null
+++ b/cordova-lib/spec-cordova/platforms/platforms.spec.js
@@ -0,0 +1,72 @@
+/**
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+*/
+
+var path = require('path');
+var rewire = require('rewire');
+
+var util = require('../../src/cordova/util');
+var platforms = rewire('../../src/platforms/platforms');
+
+var CORDOVA_ROOT = path.join(__dirname, '../fixtures/projects/platformApi');
+var PLATFORM_WITH_API = path.join(CORDOVA_ROOT, 'platforms/windows');
+var PLATFORM_WOUT_API = path.join(CORDOVA_ROOT, 'platforms/android');
+
+var MockPlatformApi = require(path.join(PLATFORM_WITH_API, 'cordova', 'Api'));
+var PlatformApiPoly = require('../../src/platforms/PlatformApiPoly');
+
+describe('getPlatformApi method', function () {
+    var isCordova;
+
+    beforeEach(function () {
+        // reset api cache after each spec
+        platforms.__set__('cachedApis', {});
+        isCordova = spyOn(util, 'isCordova').andReturn(CORDOVA_ROOT);
+    });
+
+    it('should return PlatformApi class defined by platform', function () {
+        var platformApi = platforms.getPlatformApi('windows', PLATFORM_WITH_API);
+        expect(platformApi).toEqual(jasmine.any(MockPlatformApi));
+    });
+
+    it('should return PlatformApi polyfill if PlatformApi is not defined by platform', function () {
+        var platformApi = platforms.getPlatformApi('android', PLATFORM_WOUT_API);
+        expect(platformApi).toEqual(jasmine.any(PlatformApiPoly));
+    });
+
+    it('should cache PlatformApi instance for further calls', function () {
+        var platformApi = platforms.getPlatformApi('windows', PLATFORM_WITH_API);
+        expect(platformApi.fakeProperty).not.toBeDefined();
+        platformApi.fakeProperty = 'fakeValue';
+        expect(platforms.getPlatformApi('windows', PLATFORM_WITH_API).fakeProperty).toBe('fakeValue');
+    });
+
+    it('should succeed if called inside of cordova project w/out platformRoot param', function () {
+        var platformApi = platforms.getPlatformApi('windows');
+        expect(platformApi instanceof MockPlatformApi).toBeTruthy();
+    });
+
+    it('should throw if called outside of cordova project w/out platformRoot param', function () {
+        isCordova.andReturn(false);
+        expect(function () { platforms.getPlatformApi('windows'); }).toThrow();
+    });
+
+    it('should throw for unknown platform', function () {
+        expect(function () { platforms.getPlatformApi('invalid_platform'); }).toThrow();
+    });
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/prepare.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/prepare.spec.js b/cordova-lib/spec-cordova/prepare.spec.js
index 666394c..bf9d31a 100644
--- a/cordova-lib/spec-cordova/prepare.spec.js
+++ b/cordova-lib/spec-cordova/prepare.spec.js
@@ -18,7 +18,6 @@
 */
 
 var shell = require('shelljs'),
-    plugman = require('../src/plugman/plugman'),
     PlatformJson = require('../src/plugman/util/PlatformJson'),
     path = require('path'),
     util = require('../src/cordova/util'),
@@ -59,35 +58,33 @@ describe('prepare command', function() {
         list_platforms,
         fire,
         parsers = {},
-        plugman_prepare,
         find_plugins,
         cp,
         mkdir,
-        load;
-    beforeEach(function() {
-        is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
-        cd_project_root = spyOn(util, 'cdProjectRoot').andReturn(project_dir);
-        list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
-        fire = spyOn(HooksRunner.prototype, 'fire').andReturn(Q());
+        load, platformApi, getPlatformApi;
 
-        spyOn(platforms, 'getPlatformProject').andCallFake(function(platform, rootDir) {
+    beforeEach(function () {
+        getPlatformApi = spyOn(platforms, 'getPlatformApi').andCallFake(function (platform, rootDir) {
             return {
-                update_www: jasmine.createSpy(platform + ' update_www'),
-                cordovajs_path: function(libDir) { return 'path/to/cordova.js/in/.cordova/lib';},
-                www_dir:function() { return path.join(project_dir, 'platforms', platform, 'www'); },
-                config_xml: function () { return path.join(project_dir, 'platforms', platform, 'www', 'config.xml');},
-                update_project: function () { return Q();},
+                prepare: jasmine.createSpy('prepare').andReturn(Q()),
+                getPlatformInfo: jasmine.createSpy('getPlatformInfo').andReturn({
+                    locations: {
+                        www: path.join(project_dir, 'platforms', platform, 'www')
+                    }
+                }),
             };
         });
+        is_cordova = spyOn(util, 'isCordova').andReturn(project_dir);
+        cd_project_root = spyOn(util, 'cdProjectRoot').andReturn(project_dir);
+        list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
+        fire = spyOn(HooksRunner.prototype, 'fire').andReturn(Q());
 
-        plugman_prepare = spyOn(plugman, 'prepare').andReturn(Q());
         find_plugins = spyOn(util, 'findPlugins').andReturn([]);
         spyOn(PlatformJson, 'load').andReturn(new PlatformJson(null, null, {}));
         spyOn(PlatformJson.prototype, 'save');
         load = spyOn(lazy_load, 'based_on_config').andReturn(Q());
         cp = spyOn(shell, 'cp').andReturn(true);
         mkdir = spyOn(shell, 'mkdir');
-        spyOn(prepare, '_mergeXml');
         spyOn(ConfigParser.prototype, 'write');
         spyOn(xmlHelpers, 'parseElementtreeSync').andCallFake(function() {
             return new et.ElementTree(et.XML(TEST_XML));
@@ -122,26 +119,17 @@ describe('prepare command', function() {
                 expect(err).toBeUndefined();
             }).fin(done);
         });
-        it('should invoke each platform\'s parser\'s update_project method', function(done) {
+        it('should get PlatformApi instance for each platform and invoke its\' run method', function(done) {
             prepare().then(function() {
                 supported_platforms.forEach(function(p) {
                     expect(parsers[p]).toHaveBeenCalled();
+                    expect(getPlatformApi).toHaveBeenCalledWith(p);
                 });
+                expect(platformApi.run).toHaveBeenCalled();
             }, function(err) {
                 expect(err).toBeUndefined();
             }).fin(done);
         });
-        describe('plugman integration', function() {
-            it('should invoke plugman.prepare after update_project', function(done) {
-                prepare().then(function() {
-                    supported_platforms.forEach(function(p) {
-                        expect(plugman_prepare).toHaveBeenCalled();
-                    });
-                }, function(err) {
-                    expect(err).toBeUndefined();
-                }).fin(done);
-            });
-        });
     });
 
     describe('hooks', function() {
@@ -178,118 +166,3 @@ describe('prepare command', function() {
         });
     });
 });
-
-describe('prepare._mergeXml', function () {
-    var dstXml;
-    beforeEach(function() {
-        dstXml = et.XML(TEST_XML);
-    });
-    it('should merge attributes and text of the root element without clobbering', function () {
-        var testXml = et.XML('<widget foo="bar" id="NOTANID">TEXT</widget>');
-        prepare._mergeXml(testXml, dstXml);
-        expect(dstXml.attrib.foo).toEqual('bar');
-        expect(dstXml.attrib.id).not.toEqual('NOTANID');
-        expect(dstXml.text).not.toEqual('TEXT');
-    });
-
-    it('should merge attributes and text of the root element with clobbering', function () {
-        var testXml = et.XML('<widget foo="bar" id="NOTANID">TEXT</widget>');
-        prepare._mergeXml(testXml, dstXml, 'foo', true);
-        expect(dstXml.attrib.foo).toEqual('bar');
-        expect(dstXml.attrib.id).toEqual('NOTANID');
-        expect(dstXml.text).toEqual('TEXT');
-    });
-
-    it('should not merge platform tags with the wrong platform', function () {
-        var testXml = et.XML('<widget><platform name="bar"><testElement testAttrib="value">testTEXT</testElement></platform></widget>'),
-            origCfg = et.tostring(dstXml);
-
-        prepare._mergeXml(testXml, dstXml, 'foo', true);
-        expect(et.tostring(dstXml)).toEqual(origCfg);
-    });
-
-    it('should merge platform tags with the correct platform', function () {
-        var testXml = et.XML('<widget><platform name="bar"><testElement testAttrib="value">testTEXT</testElement></platform></widget>'),
-            origCfg = et.tostring(dstXml);
-
-        prepare._mergeXml(testXml, dstXml, 'bar', true);
-        expect(et.tostring(dstXml)).not.toEqual(origCfg);
-        var testElement = dstXml.find('testElement');
-        expect(testElement).toBeDefined();
-        expect(testElement.attrib.testAttrib).toEqual('value');
-        expect(testElement.text).toEqual('testTEXT');
-    });
-
-    it('should merge singelton children without clobber', function () {
-        var testXml = et.XML('<widget><author testAttrib="value" href="http://www.nowhere.com">SUPER_AUTHOR</author></widget>');
-
-        prepare._mergeXml(testXml, dstXml);
-        var testElements = dstXml.findall('author');
-        expect(testElements).toBeDefined();
-        expect(testElements.length).toEqual(1);
-        expect(testElements[0].attrib.testAttrib).toEqual('value');
-        expect(testElements[0].attrib.href).toEqual('http://cordova.io');
-        expect(testElements[0].attrib.email).toEqual('dev@cordova.apache.org');
-        expect(testElements[0].text).toContain('Apache Cordova Team');
-    });
-
-    it('should clobber singelton children with clobber', function () {
-        var testXml = et.XML('<widget><author testAttrib="value" href="http://www.nowhere.com">SUPER_AUTHOR</author></widget>');
-
-        prepare._mergeXml(testXml, dstXml, '', true);
-        var testElements = dstXml.findall('author');
-        expect(testElements).toBeDefined();
-        expect(testElements.length).toEqual(1);
-        expect(testElements[0].attrib.testAttrib).toEqual('value');
-        expect(testElements[0].attrib.href).toEqual('http://www.nowhere.com');
-        expect(testElements[0].attrib.email).toEqual('dev@cordova.apache.org');
-        expect(testElements[0].text).toEqual('SUPER_AUTHOR');
-    });
-
-    it('should append non singelton children', function () {
-        var testXml = et.XML('<widget><preference num="1"/> <preference num="2"/></widget>');
-
-        prepare._mergeXml(testXml, dstXml, '', true);
-        var testElements = dstXml.findall('preference');
-        expect(testElements.length).toEqual(4);
-    });
-
-    it('should handle namespaced elements', function () {
-        var testXml = et.XML('<widget><foo:bar testAttrib="value">testText</foo:bar></widget>');
-
-        prepare._mergeXml(testXml, dstXml, 'foo', true);
-        var testElement = dstXml.find('foo:bar');
-        expect(testElement).toBeDefined();
-        expect(testElement.attrib.testAttrib).toEqual('value');
-        expect(testElement.text).toEqual('testText');
-    });
-
-    it('should not append duplicate non singelton children', function () {
-        var testXml = et.XML('<widget><preference name="fullscreen" value="true"/></widget>');
-
-        prepare._mergeXml(testXml, dstXml, '', true);
-        var testElements = dstXml.findall('preference');
-        expect(testElements.length).toEqual(2);
-    });
-
-    it('should not skip partial duplicate non singelton children', function () {
-        //remove access tags from dstXML
-        var testElements = dstXml.findall('access');
-        for(var i = 0; i < testElements.length; i++) {
-            dstXml.remove(testElements[i]);
-        }
-        testElements = dstXml.findall('access');
-        expect(testElements.length).toEqual(0);
-        //add an external whitelist access tag
-        var testXml = et.XML('<widget><access origin="*" launch-external="yes"/></widget>');
-        prepare._mergeXml(testXml, dstXml, '', true);
-        testElements = dstXml.findall('access');
-        expect(testElements.length).toEqual(1);
-        //add an internal whitelist access tag
-        testXml = et.XML('<widget><access origin="*"/></widget>');
-        prepare._mergeXml(testXml, dstXml, '', true);
-        testElements = dstXml.findall('access');
-        expect(testElements.length).toEqual(2);
-
-    });    
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/run.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/run.spec.js b/cordova-lib/spec-cordova/run.spec.js
index 6271cfe..627451b 100644
--- a/cordova-lib/spec-cordova/run.spec.js
+++ b/cordova-lib/spec-cordova/run.spec.js
@@ -18,8 +18,6 @@
 */
 var cordova = require('../src/cordova/cordova'),
     platforms = require('../src/platforms/platforms'),
-    superspawn = require('../src/cordova/superspawn'),
-    path = require('path'),
     HooksRunner = require('../src/hooks/HooksRunner'),
     Q = require('q'),
     util = require('../src/cordova/util');
@@ -27,7 +25,7 @@ var cordova = require('../src/cordova/cordova'),
 var supported_platforms = Object.keys(platforms).filter(function(p) { return p != 'www'; });
 
 describe('run command', function() {
-    var is_cordova, cd_project_root, list_platforms, fire;
+    var is_cordova, cd_project_root, list_platforms, fire, platformApi, getPlatformApi;
     var project_dir = '/some/path';
     var prepare_spy;
 
@@ -37,7 +35,8 @@ describe('run command', function() {
         list_platforms = spyOn(util, 'listPlatforms').andReturn(supported_platforms);
         fire = spyOn(HooksRunner.prototype, 'fire').andReturn(Q());
         prepare_spy = spyOn(cordova.raw, 'prepare').andReturn(Q());
-        spyOn(superspawn, 'spawn').andReturn(Q);
+        platformApi = { run: jasmine.createSpy('run').andReturn(Q()) };
+        getPlatformApi = spyOn(platforms, 'getPlatformApi').andReturn(platformApi);
     });
     describe('failure', function() {
         it('should not run inside a Cordova-based project with no added platforms by calling util.listPlatforms', function(done) {
@@ -61,19 +60,26 @@ describe('run command', function() {
     });
 
     describe('success', function() {
-        it('should run inside a Cordova-based project with at least one added platform and call prepare and shell out to the run script', function(done) {
+        it('should call prepare before actually run platform ', function(done) {
             cordova.raw.run(['android','ios']).then(function() {
                 expect(prepare_spy).toHaveBeenCalledWith({ platforms: [ 'android', 'ios' ], verbose: false, options: [] });
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'android', 'cordova', 'run'), [], jasmine.any(Object));
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'ios', 'cordova', 'run'), [], jasmine.any(Object));
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
+        });
+        it('should get PlatformApi instance for each platform and call its\' run method', function(done) {
+            cordova.raw.run(['android','ios']).then(function() {
+                expect(getPlatformApi).toHaveBeenCalledWith('android');
+                expect(getPlatformApi).toHaveBeenCalledWith('ios');
+                expect(platformApi.run).toHaveBeenCalled();
             }, function(err) {
                 expect(err).toBeUndefined();
             }).fin(done);
         });
         it('should pass down parameters', function(done) {
-            cordova.raw.run({platforms: ['blackberry10'], options:['--password', '1q1q']}).then(function() {
-                expect(prepare_spy).toHaveBeenCalledWith({ platforms: [ 'blackberry10' ], options: [ '--password', '1q1q' ], verbose: false });
-                expect(superspawn.spawn).toHaveBeenCalledWith(path.join(project_dir, 'platforms', 'blackberry10', 'cordova', 'run'), ['--password', '1q1q'], jasmine.any(Object));
+            cordova.raw.run({platforms: ['blackberry10'], options:{password: '1q1q'}}).then(function() {
+                expect(prepare_spy).toHaveBeenCalledWith({ platforms: [ 'blackberry10' ], options: { password: '1q1q' }, verbose: false });
+                expect(platformApi.run).toHaveBeenCalledWith({password: '1q1q'});
             }, function(err) {
                 expect(err).toBeUndefined();
             }).fin(done);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/save.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/save.spec.js b/cordova-lib/spec-cordova/save.spec.js
index a04dc4b..24a6128 100644
--- a/cordova-lib/spec-cordova/save.spec.js
+++ b/cordova-lib/spec-cordova/save.spec.js
@@ -23,9 +23,11 @@ describe('(save flag)', function () {
         helpers     = require('./helpers'),
         path        = require('path'),
         Q           = require('q'),
+        fs          = require('fs'),
         shell       = require('shelljs'),
         util        = require('../src/cordova/util'),
         prepare     = require('../src/cordova/prepare'),
+        PlatformApi = require('../src/platforms/PlatformApiPoly'),
         platform    = rewire('../src/cordova/platform');
 
     var appName             = 'testApp',
@@ -54,9 +56,8 @@ describe('(save flag)', function () {
         timeout             = 60 * 1000;
 
     //mock variables
-    var revert_copy_cordova_js,
-        revert_copy_cordovajs_src,
-        revert_install_plugins_for_new_platform;
+    var revert_install_plugins_for_new_platform,
+        createPlatformOrig = PlatformApi.createPlatform;
 
     beforeEach(function (done) {
         // initial cleanup
@@ -68,9 +69,10 @@ describe('(save flag)', function () {
         spyOn(util, 'cdProjectRoot').andReturn(appPath);
         spyOn(cordova.raw, 'prepare').andReturn(Q());
 
+        spyOn(PlatformApi, 'createPlatform').andReturn(Q());
+        spyOn(PlatformApi, 'updatePlatform').andReturn(Q());
+
         //rewire mocks
-        revert_copy_cordova_js = platform.__set__('copy_cordova_js', function () {});
-        revert_copy_cordovajs_src = platform.__set__('copy_cordovajs_src', function () {});
         revert_install_plugins_for_new_platform = platform.__set__('installPluginsForNewPlatform', function () { return Q(); });
 
        //creating test app
@@ -86,8 +88,6 @@ describe('(save flag)', function () {
     }, timeout);
 
     afterEach(function () {
-        revert_copy_cordova_js();
-        revert_copy_cordovajs_src();
         revert_install_plugins_for_new_platform();
     });
 
@@ -232,7 +232,11 @@ describe('(save flag)', function () {
             helpers.setEngineSpec(appPath, platformName, platformVersionNew);
             platform('add', platformName + '@' + platformVersionNew)
             .then(function () {
-                return cordova.raw.platform('update', platformName + '@' + platformVersionOld, { 'save': true });
+                var fsExistsSync = fs.existsSync.bind(fs);
+                spyOn(fs, 'existsSync').andCallFake(function (somePath) {
+                    return (somePath === path.join(appPath, 'platforms', platformName)) || fsExistsSync(somePath);
+                });
+                return platform('update', platformName + '@' + platformVersionOld, { 'save': true });
             }).then(function () {
                 expect(helpers.getEngineSpec(appPath, platformName)).toBe('~' + platformVersionOld);
                 done();
@@ -247,7 +251,11 @@ describe('(save flag)', function () {
             helpers.setEngineSpec(appPath, platformName, platformVersionNew);
             platform('add', platformName + '@' + platformVersionNew)
             .then(function () {
-                return cordova.raw.platform('update', platformGitUrl, { 'save': true });
+                var fsExistsSync = fs.existsSync.bind(fs);
+                spyOn(fs, 'existsSync').andCallFake(function (somePath) {
+                    return (somePath === path.join(appPath, 'platforms', platformName)) || fsExistsSync(somePath);
+                });
+                return platform('update', platformGitUrl, { 'save': true });
             }).then(function () {
                 var spec = helpers.getEngineSpec(appPath, platformName);
                 expect(spec).not.toBe(null);
@@ -437,6 +445,11 @@ describe('(save flag)', function () {
     });
 
     describe('prepare', function () {
+        beforeEach(function () {
+            // Restore back mocked createPlatform functionality
+            PlatformApi.createPlatform = createPlatformOrig;
+        });
+
         it('spec.23 should restore all platforms and plugins', function (done) {
             helpers.setEngineSpec(appPath, platformName, platformLocalPath);
             helpers.setPluginSpec(appPath, localPluginName, localPluginPath);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-cordova/xml-helpers.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/xml-helpers.spec.js b/cordova-lib/spec-cordova/xml-helpers.spec.js
index 25e6c8f..ade3c47 100644
--- a/cordova-lib/spec-cordova/xml-helpers.spec.js
+++ b/cordova-lib/spec-cordova/xml-helpers.spec.js
@@ -34,6 +34,23 @@ var path = require('path')
   , goodbyeTag = et.XML('<h1>GOODBYE</h1>')
   , helloTagTwo = et.XML('<h1>  HELLO  </h1>');
 
+var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
+    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
+    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
+    '        id        = "io.cordova.hellocordova"\n' +
+    '        version   = "0.0.1">\n' +
+    '    <name>Hello Cordova</name>\n' +
+    '    <description>\n' +
+    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
+    '    </description>\n' +
+    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
+    '        Apache Cordova Team\n' +
+    '    </author>\n' +
+    '    <content src="index.html" />\n' +
+    '    <access origin="*" />\n' +
+    '    <preference name="fullscreen" value="true" />\n' +
+    '    <preference name="webviewbounce" value="true" />\n' +
+    '</widget>\n';
 
 describe('xml-helpers', function(){
     describe('parseElementtreeSync', function() {
@@ -136,4 +153,119 @@ describe('xml-helpers', function(){
             expect(config_xml.findall('access').length).toEqual(3);
         });
     });
+
+    describe('mergeXml', function () {
+        var dstXml;
+        beforeEach(function() {
+            dstXml = et.XML(TEST_XML);
+        });
+        it('should merge attributes and text of the root element without clobbering', function () {
+            var testXml = et.XML('<widget foo="bar" id="NOTANID">TEXT</widget>');
+            xml_helpers.mergeXml(testXml, dstXml);
+            expect(dstXml.attrib.foo).toEqual('bar');
+            expect(dstXml.attrib.id).not.toEqual('NOTANID');
+            expect(dstXml.text).not.toEqual('TEXT');
+        });
+
+        it('should merge attributes and text of the root element with clobbering', function () {
+            var testXml = et.XML('<widget foo="bar" id="NOTANID">TEXT</widget>');
+            xml_helpers.mergeXml(testXml, dstXml, 'foo', true);
+            expect(dstXml.attrib.foo).toEqual('bar');
+            expect(dstXml.attrib.id).toEqual('NOTANID');
+            expect(dstXml.text).toEqual('TEXT');
+        });
+
+        it('should not merge platform tags with the wrong platform', function () {
+            var testXml = et.XML('<widget><platform name="bar"><testElement testAttrib="value">testTEXT</testElement></platform></widget>'),
+                origCfg = et.tostring(dstXml);
+
+            xml_helpers.mergeXml(testXml, dstXml, 'foo', true);
+            expect(et.tostring(dstXml)).toEqual(origCfg);
+        });
+
+        it('should merge platform tags with the correct platform', function () {
+            var testXml = et.XML('<widget><platform name="bar"><testElement testAttrib="value">testTEXT</testElement></platform></widget>'),
+                origCfg = et.tostring(dstXml);
+
+            xml_helpers.mergeXml(testXml, dstXml, 'bar', true);
+            expect(et.tostring(dstXml)).not.toEqual(origCfg);
+            var testElement = dstXml.find('testElement');
+            expect(testElement).toBeDefined();
+            expect(testElement.attrib.testAttrib).toEqual('value');
+            expect(testElement.text).toEqual('testTEXT');
+        });
+
+        it('should merge singelton children without clobber', function () {
+            var testXml = et.XML('<widget><author testAttrib="value" href="http://www.nowhere.com">SUPER_AUTHOR</author></widget>');
+
+            xml_helpers.mergeXml(testXml, dstXml);
+            var testElements = dstXml.findall('author');
+            expect(testElements).toBeDefined();
+            expect(testElements.length).toEqual(1);
+            expect(testElements[0].attrib.testAttrib).toEqual('value');
+            expect(testElements[0].attrib.href).toEqual('http://cordova.io');
+            expect(testElements[0].attrib.email).toEqual('dev@cordova.apache.org');
+            expect(testElements[0].text).toContain('Apache Cordova Team');
+        });
+
+        it('should clobber singelton children with clobber', function () {
+            var testXml = et.XML('<widget><author testAttrib="value" href="http://www.nowhere.com">SUPER_AUTHOR</author></widget>');
+
+            xml_helpers.mergeXml(testXml, dstXml, '', true);
+            var testElements = dstXml.findall('author');
+            expect(testElements).toBeDefined();
+            expect(testElements.length).toEqual(1);
+            expect(testElements[0].attrib.testAttrib).toEqual('value');
+            expect(testElements[0].attrib.href).toEqual('http://www.nowhere.com');
+            expect(testElements[0].attrib.email).toEqual('dev@cordova.apache.org');
+            expect(testElements[0].text).toEqual('SUPER_AUTHOR');
+        });
+
+        it('should append non singelton children', function () {
+            var testXml = et.XML('<widget><preference num="1"/> <preference num="2"/></widget>');
+
+            xml_helpers.mergeXml(testXml, dstXml, '', true);
+            var testElements = dstXml.findall('preference');
+            expect(testElements.length).toEqual(4);
+        });
+
+        it('should handle namespaced elements', function () {
+            var testXml = et.XML('<widget><foo:bar testAttrib="value">testText</foo:bar></widget>');
+
+            xml_helpers.mergeXml(testXml, dstXml, 'foo', true);
+            var testElement = dstXml.find('foo:bar');
+            expect(testElement).toBeDefined();
+            expect(testElement.attrib.testAttrib).toEqual('value');
+            expect(testElement.text).toEqual('testText');
+        });
+
+        it('should not append duplicate non singelton children', function () {
+            var testXml = et.XML('<widget><preference name="fullscreen" value="true"/></widget>');
+
+            xml_helpers.mergeXml(testXml, dstXml, '', true);
+            var testElements = dstXml.findall('preference');
+            expect(testElements.length).toEqual(2);
+        });
+
+        it('should not skip partial duplicate non singelton children', function () {
+            //remove access tags from dstXML
+            var testElements = dstXml.findall('access');
+            for(var i = 0; i < testElements.length; i++) {
+                dstXml.remove(testElements[i]);
+            }
+            testElements = dstXml.findall('access');
+            expect(testElements.length).toEqual(0);
+            //add an external whitelist access tag
+            var testXml = et.XML('<widget><access origin="*" launch-external="yes"/></widget>');
+            xml_helpers.mergeXml(testXml, dstXml, '', true);
+            testElements = dstXml.findall('access');
+            expect(testElements.length).toEqual(1);
+            //add an internal whitelist access tag
+            testXml = et.XML('<widget><access origin="*"/></widget>');
+            xml_helpers.mergeXml(testXml, dstXml, '', true);
+            testElements = dstXml.findall('access');
+            expect(testElements.length).toEqual(2);
+
+        });
+    });
 });


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[3/5] cordova-lib git commit: CB-9597 Initial Implementation of PlatformApiPoly

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/util/config-changes.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/util/config-changes.spec.js b/cordova-lib/spec-plugman/util/config-changes.spec.js
index 09bd350..4ffd737 100644
--- a/cordova-lib/spec-plugman/util/config-changes.spec.js
+++ b/cordova-lib/spec-plugman/util/config-changes.spec.js
@@ -44,6 +44,7 @@ var configChanges = require('../../src/plugman/util/config-changes'),
 var mungeutil = require('../../src/plugman/util/munge-util');
 var PlatformJson = require('../../src/plugman/util/PlatformJson');
 var PluginInfoProvider = require('../../src/PluginInfoProvider');
+var PluginInfo = require('../../src/PluginInfo');
 
 // TODO: dont do fs so much
 
@@ -134,7 +135,7 @@ describe('config-changes module', function() {
                 var xml;
                 var dummy_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(dummyplugin, 'plugin.xml'), 'utf-8')));
                 var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(dummyplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(dummyplugin), {});
                 expect(munge.files['AndroidManifest.xml']).toBeDefined();
                 expect(munge.files['AndroidManifest.xml'].parents['/manifest/application']).toBeDefined();
                 xml = (new et.ElementTree(dummy_xml.find('./platform[@name="android"]/config-file[@target="AndroidManifest.xml"]'))).write({xml_declaration:false});
@@ -153,7 +154,7 @@ describe('config-changes module', function() {
             });
             it('should split out multiple children of config-file elements into individual leaves', function() {
                 var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(childrenplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(childrenplugin), {PACKAGE_NAME: 'com.alunny.childapp'});
                 expect(munge.files['AndroidManifest.xml']).toBeDefined();
                 expect(munge.files['AndroidManifest.xml'].parents['/manifest']).toBeDefined();
                 expect(get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />')).toBeDefined();
@@ -168,48 +169,35 @@ describe('config-changes module', function() {
             });
             it('should not use xml comments as config munge leaves', function() {
                 var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(childrenplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(childrenplugin), {});
                 expect(get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<!--library-->')).not.toBeDefined();
                 expect(get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<!-- GCM connects to Google Services. -->')).not.toBeDefined();
             });
             it('should increment config hierarchy leaves if different config-file elements target the same file + selector + xml', function() {
                 var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(configplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(configplugin), {});
                 expect(get_munge_change(munge, 'res/xml/config.xml', '/widget', '<poop />').count).toEqual(2);
             });
             it('should take into account interpolation variables', function() {
                 var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(childrenplugin, {PACKAGE_NAME:'ca.filmaj.plugins'});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(childrenplugin), {PACKAGE_NAME:'ca.filmaj.plugins'});
                 expect(get_munge_change(munge, 'AndroidManifest.xml', '/manifest', '<uses-permission android:name="ca.filmaj.plugins.permission.C2D_MESSAGE" />')).toBeDefined();
             });
             it('should create munges for platform-agnostic config.xml changes', function() {
                 var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(dummyplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(dummyplugin), {});
                 expect(get_munge_change(munge, 'config.xml', '/*', '<access origin="build.phonegap.com" />')).toBeDefined();
                 expect(get_munge_change(munge, 'config.xml', '/*', '<access origin="s3.amazonaws.com" />')).toBeDefined();
             });
-            it('should automatically add on app java identifier as PACKAGE_NAME variable for android config munges', function() {
-                shell.cp('-rf', android_two_project, temp);
-                var munger = new configChanges.PlatformMunger('android', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(varplugin, {});
-                var expected_xml = '<package>com.alunny.childapp</package>';
-                expect(get_munge_change(munge, 'AndroidManifest.xml', '/manifest', expected_xml)).toBeDefined();
-            });
         });
 
         describe('for ios projects', function() {
             beforeEach(function() {
                 shell.cp('-rf', ios_config_xml, temp);
             });
-            it('should automatically add on ios bundle identifier as PACKAGE_NAME variable for ios config munges', function() {
-                var munger = new configChanges.PlatformMunger('ios', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(varplugin, {});
-                var expected_xml = '<cfbundleid>com.example.friendstring</cfbundleid>';
-                expect(get_munge_change(munge, 'config.xml', '/widget', expected_xml)).toBeDefined();
-            });
             it('should special case framework elements for ios', function() {
                 var munger = new configChanges.PlatformMunger('ios', temp, 'unused', null, pluginInfoProvider);
-                var munge = munger.generate_plugin_config_munge(cbplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(cbplugin), {});
                 expect(munge.files['framework']).toBeDefined();
                 expect(get_munge_change(munge, 'framework', 'libsqlite3.dylib', false)).toBeDefined();
                 expect(get_munge_change(munge, 'framework', 'social.framework', true)).toBeDefined();
@@ -223,11 +211,8 @@ describe('config-changes module', function() {
             });
             it('should special case config-file elements for windows', function() {
                 var munger = new configChanges.PlatformMunger('windows', temp, 'unused', null, pluginInfoProvider);
-                // Unit testing causes a failure when the package_name function is called from generate_plugin_config_munge
-                // the results aren't really important during the unit test
-                munger.platform_handler.package_name = function() { return 'org.apache.testapppackage'; };
 
-                var munge = munger.generate_plugin_config_munge(configplugin, {});
+                var munge = munger.generate_plugin_config_munge(pluginInfoProvider.get(configplugin), {});
                 var packageAppxManifest = munge.files['package.appxmanifest'];
                 var windows80AppxManifest = munge.files['package.windows80.appxmanifest'];
                 var windows81AppxManifest = munge.files['package.windows.appxmanifest'];
@@ -257,10 +242,10 @@ describe('config-changes module', function() {
             shell.cp('-rf', android_two_project, temp);
             var platformJson = PlatformJson.load(plugins_dir, 'android');
             platformJson.root.prepare_queue.installed = [{'plugin':'org.test.plugins.dummyplugin', 'vars':{}}];
-            var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
+            var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
             var spy = spyOn(munger, 'generate_plugin_config_munge').andReturn({});
-            munger.process();
-            expect(spy).toHaveBeenCalledWith(path.join(plugins_dir, 'org.test.plugins.dummyplugin'), {});
+            munger.process(plugins_dir);
+            expect(spy).toHaveBeenCalledWith(jasmine.any(PluginInfo), {});
         });
         describe(': installation', function() {
             describe('of xml config files', function() {
@@ -273,8 +258,8 @@ describe('config-changes module', function() {
 
                     var spy = spyOn(xml_helpers, 'graftXML').andReturn(true);
 
-                    var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                    munger.process();
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                    munger.process(plugins_dir);
                     expect(spy.calls.length).toEqual(4);
                     expect(spy.argsForCall[0][2]).toEqual('/*');
                     expect(spy.argsForCall[1][2]).toEqual('/*');
@@ -287,8 +272,8 @@ describe('config-changes module', function() {
                     platformJson.addInstalledPluginToPrepareQueue('org.test.configtest', {});
 
                     var spy = spyOn(xml_helpers, 'graftXML').andReturn(true);
-                    var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                    munger.process();
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                    munger.process(plugins_dir);
                     expect(spy.calls.length).toEqual(1);
                 });
                 it('should not call graftXML for a config munge targeting a config file that does not exist', function() {
@@ -297,8 +282,8 @@ describe('config-changes module', function() {
 
                     var spy = spyOn(fs, 'readFileSync').andCallThrough();
 
-                    var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                    munger.process();
+                    var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                    munger.process(plugins_dir);
                     expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'), 'utf-8');
                 });
             });
@@ -333,8 +318,8 @@ describe('config-changes module', function() {
                 it('should call into xcode.addFramework if plugin has <framework> file defined and is ios',function() {
                     var platformJson = PlatformJson.load(plugins_dir, 'ios');
                     platformJson.addInstalledPluginToPrepareQueue('org.test.plugins.childbrowser', {});
-                    var munger = new configChanges.PlatformMunger('ios', temp, plugins_dir, platformJson, pluginInfoProvider);
-                    munger.process();
+                    var munger = new configChanges.PlatformMunger('ios', temp, platformJson, pluginInfoProvider);
+                    munger.process(plugins_dir);
                     expect(xcode_add).toHaveBeenCalledWith('libsqlite3.dylib', {weak:false});
                     expect(xcode_add).toHaveBeenCalledWith('social.framework', {weak:true});
                     expect(xcode_add).toHaveBeenCalledWith('music.framework', {weak:false});
@@ -348,8 +333,8 @@ describe('config-changes module', function() {
                 platformJson.addInstalledPluginToPrepareQueue('org.test.plugins.childbrowser', {});
                 var spy = spyOn(fs, 'readFileSync').andCallThrough();
 
-                var munger = new configChanges.PlatformMunger('ios', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('ios', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
                 expect(spy).toHaveBeenCalledWith(path.join(temp, 'SampleApp', 'SampleApp-Info.plist').replace(/\\/g, '/'), 'utf8');
             });
             it('should move successfully installed plugins from queue to installed plugins section, and include/retain vars if applicable', function() {
@@ -358,8 +343,8 @@ describe('config-changes module', function() {
                 var platformJson = PlatformJson.load(plugins_dir, 'android');
                 platformJson.addInstalledPluginToPrepareQueue('com.adobe.vars', {'API_KEY':'hi'}, true);
 
-                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
 
                 expect(platformJson.root.prepare_queue.installed.length).toEqual(0);
                 expect(platformJson.root.installed_plugins['com.adobe.vars']).toBeDefined();
@@ -374,13 +359,13 @@ describe('config-changes module', function() {
                 // Run through an "install"
                 var platformJson = PlatformJson.load(plugins_dir, 'android');
                 platformJson.addInstalledPluginToPrepareQueue('org.test.plugins.dummyplugin', {});
-                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
 
                 // Now set up an uninstall and make sure prunexml is called properly
                 platformJson.addUninstalledPluginToPrepareQueue('org.test.plugins.dummyplugin');
                 var spy = spyOn(xml_helpers, 'pruneXML').andReturn(true);
-                munger.process();
+                munger.process(plugins_dir);
                 expect(spy.calls.length).toEqual(4);
                 expect(spy.argsForCall[0][2]).toEqual('/*');
                 expect(spy.argsForCall[1][2]).toEqual('/*');
@@ -393,15 +378,16 @@ describe('config-changes module', function() {
                 // Run through an "install"
                 var platformJson = PlatformJson.load(plugins_dir, 'android');
                 platformJson.addInstalledPluginToPrepareQueue('com.adobe.vars', {'API_KEY':'canucks'});
-                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
 
                 // Now set up an uninstall and make sure prunexml is called properly
                 platformJson.addUninstalledPluginToPrepareQueue('com.adobe.vars');
                 var spy = spyOn(munger, 'generate_plugin_config_munge').andReturn({});
-                munger.process();
+                munger.process(plugins_dir);
                 var munge_params = spy.mostRecentCall.args;
-                expect(munge_params[0]).toEqual(path.join(plugins_dir, 'com.adobe.vars'));
+                expect(munge_params[0]).toEqual(jasmine.any(PluginInfo));
+                expect(munge_params[0].dir).toEqual(path.join(plugins_dir, 'com.adobe.vars'));
                 expect(munge_params[1]['API_KEY']).toEqual('canucks');
             });
             it('should not call pruneXML for a config munge that another plugin depends on', function() {
@@ -413,12 +399,12 @@ describe('config-changes module', function() {
                 var platformJson = PlatformJson.load(plugins_dir, 'android');
                 platformJson.addInstalledPluginToPrepareQueue('org.test.multiple-children', {});
                 platformJson.addInstalledPluginToPrepareQueue('org.test.shareddeps', {});
-                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
 
                 // Now set up an uninstall for multi-child plugin
                 platformJson.addUninstalledPluginToPrepareQueue('org.test.multiple-children');
-                munger.process();
+                munger.process(plugins_dir);
                 munger.save_all();
                 var am_xml = new et.ElementTree(et.XML(fs.readFileSync(path.join(temp, 'AndroidManifest.xml'), 'utf-8')));
                 var permission = am_xml.find('./uses-permission');
@@ -430,14 +416,14 @@ describe('config-changes module', function() {
                 // install a plugin
                 var platformJson = PlatformJson.load(plugins_dir, 'android');
                 platformJson.addInstalledPluginToPrepareQueue('org.test.plugins.dummyplugin', {});
-                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
 
                 // set up an uninstall for the same plugin
                 platformJson.addUninstalledPluginToPrepareQueue('org.test.plugins.dummyplugin');
 
                 var spy = spyOn(fs, 'readFileSync').andCallThrough();
-                munger.process();
+                munger.process(plugins_dir);
 
                 expect(spy).not.toHaveBeenCalledWith(path.join(temp, 'res', 'xml', 'plugins.xml'), 'utf-8');
             });
@@ -447,12 +433,12 @@ describe('config-changes module', function() {
                 // install the var plugin
                 var platformJson = PlatformJson.load(plugins_dir, 'android');
                 platformJson.addInstalledPluginToPrepareQueue('com.adobe.vars', {'API_KEY':'eat my shorts'});
-                var munger = new configChanges.PlatformMunger('android', temp, plugins_dir, platformJson, pluginInfoProvider);
-                munger.process();
+                var munger = new configChanges.PlatformMunger('android', temp, platformJson, pluginInfoProvider);
+                munger.process(plugins_dir);
 
                 // queue up an uninstall for the same plugin
                 platformJson.addUninstalledPluginToPrepareQueue('com.adobe.vars');
-                munger.process();
+                munger.process(plugins_dir);
 
                 expect(platformJson.root.prepare_queue.uninstalled.length).toEqual(0);
                 expect(platformJson.root.installed_plugins['com.adobe.vars']).not.toBeDefined();

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/PluginInfo.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/PluginInfo.js b/cordova-lib/src/PluginInfo.js
index 08b0a97..1802f24 100644
--- a/cordova-lib/src/PluginInfo.js
+++ b/cordova-lib/src/PluginInfo.js
@@ -83,7 +83,11 @@ function PluginInfo(dirname) {
             throw new Error(msg);
         }
 
-        var asset = { src: src, target: target };
+        var asset = {
+            itemType: 'asset',
+            src: src,
+            target: target
+        };
         return asset;
     }
 
@@ -258,6 +262,7 @@ function PluginInfo(dirname) {
 
     function _parseJsModule(tag) {
         var ret = {
+            itemType: 'js-module',
             name: tag.attrib.name,
             src: tag.attrib.src,
             clobbers: tag.findall('clobbers').map(function(tag) { return { target: tag.attrib.target }; }),

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/clean.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/clean.js b/cordova-lib/src/cordova/clean.js
index 8814ff6..59696ed 100644
--- a/cordova-lib/src/cordova/clean.js
+++ b/cordova-lib/src/cordova/clean.js
@@ -17,12 +17,11 @@
     under the License.
 */
 
-var path              = require('path'),
-    cordova_util      = require('./util'),
-    HooksRunner       = require('../hooks/HooksRunner'),
-    events            = require('../events'),
-    chain             = require('../util/promise-util').Q_chainmap,
-    superspawn        = require('./superspawn');
+var cordova_util = require('./util'),
+    HooksRunner  = require('../hooks/HooksRunner'),
+    events       = require('../events'),
+    chain        = require('../util/promise-util').Q_chainmap,
+    platform_lib = require('../platforms/platforms');
 
 // Returns a promise.
 module.exports = function clean(options) {
@@ -34,12 +33,9 @@ module.exports = function clean(options) {
     .then(function () {
         return chain(options.platforms, function (platform) {
             events.emit('verbose', 'Running cleanup for ' + platform + ' platform.');
-            var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'clean');
-            return superspawn.spawn(cmd, options.options, {
-                stdio: options.silent ? 'ignore' : 'inherit', // hide script output in silent mode
-                printCommand: !!options.verbose,              // print command only if --verbose specified
-                chmod: true
-            });
+            return platform_lib
+                .getPlatformApi(platform)
+                .clean();
         });
     })
     .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/compile.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/compile.js b/cordova-lib/src/cordova/compile.js
index 3728b67..dbf69ec 100644
--- a/cordova-lib/src/cordova/compile.js
+++ b/cordova-lib/src/cordova/compile.js
@@ -17,12 +17,12 @@
     under the License.
 */
 
-var path              = require('path'),
-    cordova_util      = require('./util'),
-    HooksRunner       = require('../hooks/HooksRunner'),
-    events            = require('../events'),
-    Q                 = require('q'),
-    superspawn        = require('./superspawn');
+var cordova_util = require('./util'),
+    HooksRunner  = require('../hooks/HooksRunner'),
+    events       = require('../events'),
+    Q            = require('q'),
+    promiseUtil  = require('../util/promise-util'),
+    platform_lib = require('../platforms/platforms');
 
 // Returns a promise.
 module.exports = function compile(options) {
@@ -30,18 +30,17 @@ module.exports = function compile(options) {
     options = cordova_util.preProcessOptions(options);
 
     var hooksRunner = new HooksRunner(projectRoot);
-    var ret = hooksRunner.fire('before_compile', options);
-    options.platforms.forEach(function(platform) {
-        ret = ret.then(function() {
-            var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'build');
-            return superspawn.spawn(cmd, options.options, { stdio: 'inherit', printCommand: true, chmod: true });
+    return hooksRunner.fire('before_compile', options)
+    .then(function () {
+        return promiseUtil.Q_chainmap(options.platforms, function (platform) {
+            return platform_lib
+                .getPlatformApi(platform)
+                .build(options.options);
         });
-    });
-    ret = ret.then(function() {
+    }).then(function() {
         return hooksRunner.fire('after_compile', options);
     }, function(error) {
         events.emit('log', 'ERROR building one of the platforms: ' + error + '\nYou may not have the required environment or OS to build this project');
         return Q.reject(error);
     });
-    return ret;
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/emulate.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/emulate.js b/cordova-lib/src/cordova/emulate.js
index 3b09545..67dedeb 100644
--- a/cordova-lib/src/cordova/emulate.js
+++ b/cordova-lib/src/cordova/emulate.js
@@ -17,16 +17,17 @@
     under the License.
 */
 
-var cordova_util      = require('./util'),
-    path              = require('path'),
-    HooksRunner            = require('../hooks/HooksRunner'),
-    superspawn        = require('./superspawn'),
-    Q                 = require('q');
+var cordova_util = require('./util'),
+    HooksRunner  = require('../hooks/HooksRunner'),
+    Q            = require('q'),
+    platform_lib = require('../platforms/platforms');
 
 // Returns a promise.
 module.exports = function emulate(options) {
     var projectRoot = cordova_util.cdProjectRoot();
     options = cordova_util.preProcessOptions(options);
+    options.options.device = false;
+    options.options.emulator = true;
 
     var hooksRunner = new HooksRunner(projectRoot);
     return hooksRunner.fire('before_emulate', options)
@@ -36,10 +37,9 @@ module.exports = function emulate(options) {
     }).then(function() {
         // Deploy in parallel (output gets intermixed though...)
         return Q.all(options.platforms.map(function(platform) {
-            var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'run');
-            var args = ['--emulator'].concat(options.options);
-
-            return superspawn.spawn(cmd, args, {stdio: 'inherit', printCommand: true, chmod: true});
+            return platform_lib
+                .getPlatformApi(platform)
+                .run(options.options);
         }));
     }).then(function() {
         return hooksRunner.fire('after_emulate', options);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js
index 4739f80..3e963b0 100644
--- a/cordova-lib/src/cordova/platform.js
+++ b/cordova-lib/src/cordova/platform.js
@@ -33,7 +33,6 @@ var config            = require('./config'),
     promiseutil       = require('../util/promise-util'),
     superspawn        = require('./superspawn'),
     semver            = require('semver'),
-    unorm             = require('unorm'),
     shell             = require('shelljs'),
     _                 = require('underscore'),
     PlatformJson      = require('../plugman/util/PlatformJson'),
@@ -124,59 +123,69 @@ function addHelper(cmd, hooksRunner, projectRoot, targets, opts) {
                 var platformPath = path.join(projectRoot, 'platforms', platform);
                 var platformAlreadyAdded = fs.existsSync(platformPath);
 
-                return Q().then(function() {
-                    if (cmd == 'add') {
-                        if (platformAlreadyAdded) {
-                            throw new CordovaError('Platform ' + platform + ' already added.');
-                        }
-
-                        // Remove the <platform>.json file from the plugins directory, so we start clean (otherwise we
-                        // can get into trouble not installing plugins if someone deletes the platform folder but
-                        // <platform>.json still exists).
-                        removePlatformPluginsJson(projectRoot, target);
-
-                        var template_dir = config_json && config_json.lib && config_json.lib[platform] && config_json.lib[platform].template || null;
-                        events.emit('log', 'Adding ' + platform + ' project...');
-
-                        return getCreateArgs(platDetails, projectRoot, cfg, template_dir, opts);
-                    } else if (cmd == 'update') {
-                        if (!platformAlreadyAdded) {
-                            throw new CordovaError('Platform "' + platform + '" is not yet added. See `' +
-                                cordova_util.binname + ' platform list`.');
-                        }
-                        events.emit('log', 'Updating ' + platform + ' project...');
-
-                        // CB-6976 Windows Universal Apps. Special case to upgrade from windows8 to windows platform
-                        if (platform == 'windows8' && !fs.existsSync(path.join(projectRoot, 'platforms', 'windows'))) {
-                            var platformPathWindows = path.join(projectRoot, 'platforms', 'windows');
-                            fs.renameSync(platformPath, platformPathWindows);
-                            platform = 'windows';
-                            platformPath = platformPathWindows;
-                        }
-                        // Call the platform's update script.
-                        var args = [platformPath];
-                        if (opts.link) {
-                            args.push('--link');
-                        }
-                        return args;
+                if (cmd == 'add') {
+                    if (platformAlreadyAdded) {
+                        throw new CordovaError('Platform ' + platform + ' already added.');
                     }
-                }).then(function(args) {
-                    var bin = path.join(platDetails.libDir, 'bin', cmd == 'add' ? 'create' : 'update');
-                    var copts = { stdio: 'inherit', chmod: true };
-                    if ('spawnoutput' in opts) {
-                        copts = { stdio: opts.spawnoutput };
+
+                    // Remove the <platform>.json file from the plugins directory, so we start clean (otherwise we
+                    // can get into trouble not installing plugins if someone deletes the platform folder but
+                    // <platform>.json still exists).
+                    removePlatformPluginsJson(projectRoot, target);
+                } else if (cmd == 'update') {
+                    if (!platformAlreadyAdded) {
+                        throw new CordovaError('Platform "' + platform + '" is not yet added. See `' +
+                            cordova_util.binname + ' platform list`.');
                     }
-                    return superspawn.spawn(bin, args, copts);
-                }).then(function() {
-                    var platform_www = path.join(projectRoot, 'platforms', platform, 'platform_www');
 
-                    copy_cordova_js(projectRoot, platform);
+                    // CB-6976 Windows Universal Apps. Special case to upgrade from windows8 to windows platform
+                    if (platform == 'windows8' && !fs.existsSync(path.join(projectRoot, 'platforms', 'windows'))) {
+                        var platformPathWindows = path.join(projectRoot, 'platforms', 'windows');
+                        fs.renameSync(platformPath, platformPathWindows);
+                        platform = 'windows';
+                        platformPath = platformPathWindows;
+                    }
+                }
 
-                    // only want to copy cordova-js-src once, when the platform is added
-                    if (!fs.existsSync(path.join(platform_www, 'cordova-js-src'))) {
-                        copy_cordovajs_src(projectRoot, platform, platDetails.libDir);
+                var cordovaProject = {
+                    root: projectRoot,
+                    projectConfig: cfg,
+                    locations: {
+                        www: path.join(projectRoot, 'www'),
+                        platforms: path.join(projectRoot, 'platforms'),
+                        configXml: path.join(projectRoot, 'config.xml')
                     }
-                }).then(function () {
+                };
+
+                var options = {
+                    // We need to pass a platformDetails into update/create
+                    // since PlatformApiPoly needs to know something about
+                    // platform, it is going to create.
+                    platformDetails: platDetails,
+                    link: opts.link
+                };
+
+                if (config_json && config_json.lib && config_json.lib[platform] &&
+                    config_json.lib[platform].template) {
+                    options.customTemplate = config_json.lib[platform].template;
+                }
+
+                events.emit('log', (cmd === 'add' ? 'Adding ' : 'Updating ') + platform + ' project...');
+
+                var PlatformApi;
+                try {
+                    // Try to get PlatformApi class from platform
+                    PlatformApi = require(path.resolve(platDetails.libDir, 'bin/PlatformApi'));
+                } catch (err) {
+                    PlatformApi = require('../platforms/PlatformApiPoly');
+                }
+
+                var promise = cmd === 'add' ?
+                    PlatformApi.createPlatform :
+                    PlatformApi.updatePlatform;
+
+                return promise(cordovaProject, options)
+                .then(function () {
                     // Call prepare for the current platform.
                     var prepOpts = {
                         platforms :[platform],
@@ -597,37 +606,6 @@ function hostSupports(platform) {
     return false;
 }
 
-// Returns a promise.
-function getCreateArgs(platDetails, projectRoot, cfg, template_dir, opts) {
-    var output = path.join(projectRoot, 'platforms', platDetails.platform);
-
-    var args = [];
-    if (/android|ios/.exec(platDetails.platform) && semver.gt(platDetails.version, '3.3.0')) {
-        args.push('--cli');
-    }
-
-    var pkg = cfg.packageName().replace(/[^\w.]/g,'_');
-    // CB-6992 it is necessary to normalize characters
-    // because node and shell scripts handles unicode symbols differently
-    // We need to normalize the name to NFD form since iOS uses NFD unicode form
-    var name = platDetails.platform == 'ios' ? unorm.nfd(cfg.name()) : cfg.name();
-    args.push(output, pkg, name);
-
-    var activityName = cfg.android_activityName();
-    if (activityName && platDetails.platform === 'android' && semver.gte(platDetails.version, '4.0.0-dev')) {
-        activityName = activityName.replace(/\W/g, '');
-        args.push('--activity-name', activityName);
-    }
-
-    if (template_dir) {
-        args.push(template_dir);
-    }
-    if (opts.link) {
-        args.push('--link');
-    }
-    return args;
-}
-
 function installPluginsForNewPlatform(platform, projectRoot, opts) {
     // Install all currently installed plugins into this new platform.
     var plugins_dir = path.join(projectRoot, 'plugins');
@@ -669,29 +647,6 @@ function installPluginsForNewPlatform(platform, projectRoot, opts) {
     }, Q());
 }
 
-// Copy the cordova.js file to platforms/<platform>/platform_www/
-// The www dir is nuked on each prepare so we keep cordova.js in platform_www
-function copy_cordova_js(projectRoot, platform) {
-    var platformPath = path.join(projectRoot, 'platforms', platform);
-    var parser = platforms.getPlatformProject(platform, platformPath);
-    var platform_www = path.join(platformPath, 'platform_www');
-    shell.mkdir('-p', platform_www);
-    shell.cp('-f', path.join(parser.www_dir(), 'cordova.js'), path.join(platform_www, 'cordova.js'));
-}
-
-// Copy cordova-js-src directory into platform_www directory.
-// We need these files to build cordova.js if using browserify method.
-function copy_cordovajs_src(projectRoot, platform, platLib) {
-    var platformPath = path.join(projectRoot, 'platforms', platform);
-    var parser = platforms.getPlatformProject(platform, platformPath);
-    var platform_www = path.join(platformPath, 'platform_www');
-    var cordovaJsSrcPath = parser.cordovajs_src_path(platLib);
-    //only exists for platforms that have shipped cordova-js-src directory
-    if(fs.existsSync(cordovaJsSrcPath)) {
-        shell.cp('-rf', cordovaJsSrcPath, platform_www);
-    }
-}
-
 // Remove <platform>.json file from plugins directory.
 function removePlatformPluginsJson(projectRoot, target) {
     var plugins_json = path.join(projectRoot, 'plugins', target + '.json');

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js b/cordova-lib/src/cordova/plugin.js
index 0f90de2..f49205a 100644
--- a/cordova-lib/src/cordova/plugin.js
+++ b/cordova-lib/src/cordova/plugin.js
@@ -30,7 +30,8 @@ var cordova_util  = require('./util'),
     plugman       = require('../plugman/plugman'),
     pluginMapper  = require('cordova-registry-mapper').newToOld,
     events        = require('../events'),
-    metadata      = require('../plugman/util/metadata');
+    metadata      = require('../plugman/util/metadata'),
+    chainMap      = require('../util/promise-util').Q_chainmap;
 
 // Returns a promise.
 module.exports = function plugin(command, targets, opts) {
@@ -134,14 +135,23 @@ module.exports = function plugin(command, targets, opts) {
                         // Fetch the plugin first.
                         events.emit('verbose', 'Calling plugman.fetch on plugin "' + target + '"');
 
-                        return plugman.raw.fetch(target, pluginPath, { searchpath: searchPath, noregistry: opts.noregistry, link: opts.link,
-                                                                       pluginInfoProvider: pluginInfoProvider, variables: opts.cli_variables,
-                                                                       is_top_level: true });
+                        var fetchOptions = {
+                            searchpath: searchPath,
+                            noregistry: opts.noregistry,
+                            link: opts.link,
+                            pluginInfoProvider: pluginInfoProvider,
+                            variables: opts.cli_variables,
+                            is_top_level: true
+                        };
+
+                        return plugman.raw.fetch(target, pluginPath, fetchOptions)
+                        .then(function (directory) {
+                            return pluginInfoProvider.get(directory);
+                        });
                     })
-                    .then(function(dir){
+                    .then(function(pluginInfo){
                         // save to config.xml
-                        if(saveToConfigXmlOn(config_json,opts)){
-                            var pluginInfo =  pluginInfoProvider.get(dir);
+                        if(saveToConfigXmlOn(config_json, opts)){
 
                             var attributes = {};
                             attributes.name = pluginInfo.id;
@@ -149,77 +159,48 @@ module.exports = function plugin(command, targets, opts) {
                             var src = parseSource(target, opts);
                             attributes.spec = src ? src : '~' + pluginInfo.version;
 
-                            var variables = [];
-                            if (opts.cli_variables) {
-                                for (var varname in opts.cli_variables) {
-                                    if (opts.cli_variables.hasOwnProperty(varname)) {
-                                        variables.push({name: varname, value: opts.cli_variables[varname]});
-                                    }
-                                }
-                            }
+                            var variables = Object.keys(opts.cli_variables || [])
+                            .map(function (variableName) {
+                                return {name: variableName, value: opts.cli_variables[variableName]};
+                            });
+
                             cfg.removePlugin(pluginInfo.id);
                             cfg.addPlugin(attributes, variables);
                             cfg.write();
                             events.emit('results', 'Saved plugin info for "' + pluginInfo.id + '" to config.xml');
                         }
-                        return dir;
+                        return pluginInfo;
                     })
-                    .then(function(dir) {
+                    .then(function(pluginInfo) {
                         // Validate top-level required variables
-                        var pluginVariables = pluginInfoProvider.get(dir).getPreferences();
-                        var requiredVariables = [];
-
-                        for(var i in pluginVariables) {
-                            var v = pluginVariables[i];
+                        var pluginVariables = pluginInfo.getPreferences();
+                        var missingVariables = Object.keys(pluginVariables)
+                        .filter(function (variableName) {
                             // discard variables with default value
-                            if (!v) {
-                                requiredVariables.push(i);
-                            }
-                        }
-
-                        opts.cli_variables = opts.cli_variables || {}; 
-                        var missingVariables = requiredVariables.filter(function (v) {
-                            return !(v in opts.cli_variables);
+                            return !(pluginVariables[variableName] || opts.cli_variables[variableName]);
                         });
 
                         if (missingVariables.length) {
-                            shell.rm('-rf', dir);
+                            shell.rm('-rf', pluginInfo.dir);
                             var msg = 'Variable(s) missing (use: --variable ' + missingVariables.join('=value --variable ') + '=value).';
                             return Q.reject(new CordovaError(msg));
                         }
+
                         // Iterate (in serial!) over all platforms in the project and install the plugin.
-                        return platformList.reduce(function(soFar, platform) {
-                            return soFar.then(function() {
-                                var platformRoot = path.join(projectRoot, 'platforms', platform),
-                                    options = {
-                                        cli_variables: opts.cli_variables || {},
-                                        browserify: opts.browserify || false,
-                                        searchpath: searchPath,
-                                        noregistry: opts.noregistry,
-                                        link: opts.link,
-                                        pluginInfoProvider: pluginInfoProvider
-                                    },
-                                    tokens,
-                                    key,
-                                    i;
-
-                                // TODO: Remove this. CLI vars are passed as part of the opts object after "nopt" refactoring.
-                                // Keeping for now for compatibility for API users.
-                                //parse variables into cli_variables
-                                for (i=0; i< opts.options.length; i++) {
-                                    if (opts.options[i] === '--variable' && typeof opts.options[++i] === 'string') {
-                                        tokens = opts.options[i].split('=');
-                                        key = tokens.shift().toUpperCase();
-                                        if (/^[\w-_]+$/.test(key)) {
-                                            options.cli_variables[key] = tokens.join('=');
-                                        }
-                                    }
-                                }
-
-                                events.emit('verbose', 'Calling plugman.install on plugin "' + dir + '" for platform "' + platform + '" with options "' + JSON.stringify(options)  + '"');
-                                return plugman.raw.install(platform, platformRoot, path.basename(dir), pluginPath, options);
-                            });
-                        }, Q());
+                        return chainMap(platformList, function (platform) {
+                            var platformRoot = path.join(projectRoot, 'platforms', platform),
+                            options = {
+                                cli_variables: opts.cli_variables || {},
+                                browserify: opts.browserify || false,
+                                searchpath: searchPath,
+                                noregistry: opts.noregistry,
+                                link: opts.link,
+                                pluginInfoProvider: pluginInfoProvider
+                            };
+
+                            events.emit('verbose', 'Calling plugman.install on plugin "' + pluginInfo.dir + '" for platform "' + platform);
+                            return plugman.raw.install(platform, platformRoot, path.basename(pluginInfo.dir), pluginPath, options);
+                        });
                     });
                 }, Q()); // end Q.all
             }).then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/prepare.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/prepare.js b/cordova-lib/src/cordova/prepare.js
index 9e27833..4125bb3 100644
--- a/cordova-lib/src/cordova/prepare.js
+++ b/cordova-lib/src/cordova/prepare.js
@@ -19,201 +19,64 @@
 
 var cordova_util      = require('./util'),
     ConfigParser      = require('../configparser/ConfigParser'),
-    path              = require('path'),
     platforms         = require('../platforms/platforms'),
-    fs                = require('fs'),
-    shell             = require('shelljs'),
-    et                = require('elementtree'),
     HooksRunner       = require('../hooks/HooksRunner'),
-    events            = require('../events'),
     Q                 = require('q'),
-    plugman           = require('../plugman/plugman'),
-    PlatformMunger    = require('../plugman/util/config-changes').PlatformMunger,
-    PlatformJson      = require('../plugman/util/PlatformJson'),
     restore           = require('./restore-util'),
+    path              = require('path'),
+    browserify = require('../plugman/browserify'),
     config            = require('./config');
 
-
-var PluginInfoProvider = require('../PluginInfoProvider');
-
 // Returns a promise.
 exports = module.exports = prepare;
 function prepare(options) {
     var projectRoot = cordova_util.cdProjectRoot();
-    var xml = cordova_util.projectConfig(projectRoot);
     var config_json = config.read(projectRoot);
-
-    if (!options) {
-        options = {
-            verbose: false,
-            platforms: [],
-            options: []
-        };
-    }
-
-    options.searchpath = options.searchpath || config_json.plugin_search_path;
+    options = options || { verbose: false, platforms: [], options: [] };
 
     var hooksRunner = new HooksRunner(projectRoot);
     return hooksRunner.fire('before_prepare', options)
     .then(function(){
-        return restore.installPlatformsFromConfigXML(options.platforms);
-    })
-    .then(function(){
-        options = cordova_util.preProcessOptions(options);
-        var paths = options.platforms.map(function(p) {
-            var platform_path = path.join(projectRoot, 'platforms', p);
-            var parser = platforms.getPlatformProject(p, platform_path);
-            return parser.www_dir();
-        });
-        options.paths = paths;
+        var platformsToRestore = options && options.platforms || [];
+        return restore.installPlatformsFromConfigXML(platformsToRestore);
     })
     .then(function() {
-        var pluginInfoProvider = new PluginInfoProvider();
-
+        options = cordova_util.preProcessOptions(options);
+        options.searchpath = options.searchpath || config_json.plugin_search_path;
         // Iterate over each added platform
         return Q.all(options.platforms.map(function(platform) {
-            var platformPath = path.join(projectRoot, 'platforms', platform);
-
-            var parser = platforms.getPlatformProject(platform, platformPath);
-            var defaults_xml_path = path.join(platformPath, 'cordova', 'defaults.xml');
-            // If defaults.xml is present, overwrite platform config.xml with
-            // it Otherwise save whatever is there as defaults so it can be
-            // restored or copy project config into platform if none exists.
-            if (fs.existsSync(defaults_xml_path)) {
-                shell.cp('-f', defaults_xml_path, parser.config_xml());
-                events.emit('verbose', 'Generating config.xml from defaults for platform "' + platform + '"');
-            } else {
-                if(fs.existsSync(parser.config_xml())){
-                    shell.cp('-f', parser.config_xml(), defaults_xml_path);
-                }else{
-                    shell.cp('-f', xml, parser.config_xml());
+            // TODO: this need to be replaced by real projectInfo
+            // instance for current project.
+            var project = {
+                root: projectRoot,
+                projectConfig: new ConfigParser(cordova_util.projectConfig(projectRoot)),
+                locations: {
+                    plugins: path.join(projectRoot, 'plugins'),
+                    www: cordova_util.projectWww(projectRoot)
                 }
-            }
-
-            var stagingPath = path.join(platformPath, '.staging');
-            if (fs.existsSync(stagingPath)) {
-                events.emit('log', 'Deleting now-obsolete intermediate directory: ' + stagingPath);
-                shell.rm('-rf', stagingPath);
-            }
-
-            // Replace the existing web assets with the app master versions
-            parser.update_www();
-
-            // Call plugman --prepare for this platform. sets up js-modules appropriately.
-            var plugins_dir = path.join(projectRoot, 'plugins');
-            events.emit('verbose', 'Calling plugman.prepare for platform "' + platform + '"');
-
-            if (options.browserify) {
-                plugman.prepare = require('../plugman/prepare-browserify');
-            }
-
-            return plugman.prepare(platformPath, platform, plugins_dir, null, true, pluginInfoProvider)
+            };
+
+            // platformApi prepare takes care of all functionality
+            // which previously had been executed by cordova.prepare:
+            //   - reset config.xml and then merge changes from project's one,
+            //   - update www directory from project's one and merge assets from platform_www,
+            //   - reapply config changes, made by plugins,
+            //   - update platform's project
+            // Please note that plugins' changes, such as installes js files, assets and
+            // config changes is not being reinstalled on each prepare.
+            var platformApi = platforms.getPlatformApi(platform);
+            return platformApi.prepare(project)
             .then(function () {
-                // Remove cordova-js-src from application www directory
-                // otherwise it will be included into resultant app bundle
-                var cordovaJsSrcPath = path.join(platformPath, 'www/cordova-js-src');
-                if(fs.existsSync(cordovaJsSrcPath)) {
-                    shell.rm('-rf', cordovaJsSrcPath);
-                }
-            }).then(function () {
-                // Make sure that config changes for each existing plugin is in place
-                var platformJson = PlatformJson.load(plugins_dir, platform);
-                var munger = new PlatformMunger(platform, platformPath, plugins_dir, platformJson, pluginInfoProvider);
-                munger.reapply_global_munge();
-                munger.save_all();
-
-                // Update platform config.xml based on top level config.xml
-                var cfg = new ConfigParser(xml);
-                var platform_cfg = new ConfigParser(parser.config_xml());
-                exports._mergeXml(cfg.doc.getroot(), platform_cfg.doc.getroot(), platform, true);
-
-                // CB-6976 Windows Universal Apps. For smooth transition and to prevent mass api failures
-                // we allow using windows8 tag for new windows platform
-                if (platform == 'windows') {
-                    exports._mergeXml(cfg.doc.getroot(), platform_cfg.doc.getroot(), 'windows8', true);
-                }
-
-                platform_cfg.write();
-
-                return parser.update_project(cfg);
+                if (options.browserify)
+                    return browserify(project, platformApi);
             });
-        })).then(function() {
-            return hooksRunner.fire('after_prepare', options);
+        }));
+    }).then(function() {
+        options.paths = options.platforms.map(function(platform) {
+            return platforms.getPlatformApi(platform).getPlatformInfo().locations.www;
         });
+        return hooksRunner.fire('after_prepare', options);
     }).then(function () {
         return restore.installPluginsFromConfigXML(options);
     });
 }
-
-var BLACKLIST = ['platform', 'feature','plugin','engine'];
-var SINGLETONS = ['content', 'author'];
-function mergeXml(src, dest, platform, clobber) {
-    // Do nothing for blacklisted tags.
-    if (BLACKLIST.indexOf(src.tag) != -1) return;
-
-    //Handle attributes
-    Object.getOwnPropertyNames(src.attrib).forEach(function (attribute) {
-        if (clobber || !dest.attrib[attribute]) {
-            dest.attrib[attribute] = src.attrib[attribute];
-        }
-    });
-    //Handle text
-    if (src.text && (clobber || !dest.text)) {
-        dest.text = src.text;
-    }
-    //Handle platform
-    if (platform) {
-        src.findall('platform[@name="' + platform + '"]').forEach(function (platformElement) {
-            platformElement.getchildren().forEach(mergeChild);
-        });
-    }
-
-    //Handle children
-    src.getchildren().forEach(mergeChild);
-
-    function mergeChild (srcChild) {
-        var srcTag = srcChild.tag,
-            destChild = new et.Element(srcTag),
-            foundChild,
-            query = srcTag + '',
-            shouldMerge = true;
-
-        if (BLACKLIST.indexOf(srcTag) === -1) {
-            if (SINGLETONS.indexOf(srcTag) !== -1) {
-                foundChild = dest.find(query);
-                if (foundChild) {
-                    destChild = foundChild;
-                    dest.remove(destChild);
-                }
-            } else {
-                //Check for an exact match and if you find one don't add
-                Object.getOwnPropertyNames(srcChild.attrib).forEach(function (attribute) {
-                    query += '[@' + attribute + '="' + srcChild.attrib[attribute] + '"]';
-                });
-                var foundChildren = dest.findall(query);
-                for(var i = 0; i < foundChildren.length; i++) {
-                    foundChild = foundChildren[i];
-                    if (foundChild && textMatch(srcChild, foundChild) && (Object.keys(srcChild.attrib).length==Object.keys(foundChild.attrib).length)) {
-                        destChild = foundChild;
-                        dest.remove(destChild);
-                        shouldMerge = false;
-                        break;
-                    }
-                }
-            }
-
-            mergeXml(srcChild, destChild, platform, clobber && shouldMerge);
-            dest.append(destChild);
-        }
-    }
-}
-
-// Expose for testing.
-exports._mergeXml = mergeXml;
-
-
-function textMatch(elm1, elm2) {
-    var text1 = elm1.text ? elm1.text.replace(/\s+/, '') : '',
-        text2 = elm2.text ? elm2.text.replace(/\s+/, '') : '';
-    return (text1 === '' || text1 === text2);
-}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/requirements.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/requirements.js b/cordova-lib/src/cordova/requirements.js
index 46a7284..a0fa79b 100644
--- a/cordova-lib/src/cordova/requirements.js
+++ b/cordova-lib/src/cordova/requirements.js
@@ -18,51 +18,31 @@
 */
 
 var cordova_util = require('./util');
-var events       = require('../events');
-var path         = require('path');
 var Q            = require('q');
 var CordovaError = require('../CordovaError');
+var knownPlatforms = require('../platforms/platforms');
 
 /**
  * Runs requirements check against platforms specified in 'platfoms' argument
  *
- * @param  {String[]} platforms List of platforms for requirements check. If none, all
- *                                      platforms, added to project will be checked
+ * @param  {String[]} platforms List of platforms for requirements check. If
+ *   none, all platforms, added to project will be checked
  *
- * @return {Promise}            Promise fullfilled with map of platforms and requirements
- *                                      check results for each platform
+ * @return {Promise<Object>}    Promise fullfilled with map of platforms and
+ *   requirements check results for each platform.
  */
 module.exports = function check_reqs(platforms) {
     platforms = cordova_util.preProcessOptions(platforms).platforms;
 
-    var projectRoot = cordova_util.isCordova();
-    var platformsDir = path.join(projectRoot, 'platforms');
-    var platformChecks = platforms.map(function (platform) {
-        var modulePath = path.join(platformsDir, platform, 'cordova', 'lib', 'check_reqs');
-        try {
-            events.emit('verbose', 'Checking requirements for ' + platform + ' platform');
-            return require(modulePath).check_all();
-        } catch (e) {
-            var errorMsg = 'Failed to check requirements for ' + platform + ' platform. ' +
-                'check_reqs module is missing for platform. Skipping it...';
-            return Q.reject(errorMsg);
-        }
-    });
-
-    var checks = {};
-
-    return Q.allSettled(platformChecks)
+    return Q.allSettled(platforms.map(function (platform) {
+        return knownPlatforms.getPlatformApi(platform).requirements();
+    }))
     .then(function (settledChecks) {
-
-        settledChecks.forEach(function (settledCheck, idx) {
+        return settledChecks.reduce(function (result, settledCheck, idx) {
             var platformName = platforms[idx];
-            var result  = settledCheck.state === 'fulfilled' ?
+            result[platformName] = settledCheck.state === 'fulfilled' ?
                 settledCheck.value :
                 new CordovaError(settledCheck.reason);
-
-            checks[platformName] = result;
-        });
-
-        return checks;
+        }, {});
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/run.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/run.js b/cordova-lib/src/cordova/run.js
index b5c1e27..b739580 100644
--- a/cordova-lib/src/cordova/run.js
+++ b/cordova-lib/src/cordova/run.js
@@ -17,12 +17,12 @@
     under the License.
 */
 
-var cordova_util      = require('./util'),
-    path              = require('path'),
-    HooksRunner            = require('../hooks/HooksRunner'),
-    events            = require('../events'),
-    superspawn        = require('./superspawn'),
-    Q                 = require('q');
+var cordova_util = require('./util'),
+    HooksRunner  = require('../hooks/HooksRunner'),
+    events       = require('../events'),
+    Q            = require('q'),
+    platform_lib = require('../platforms/platforms');
+
 
 // Returns a promise.
 module.exports = function run(options) {
@@ -37,8 +37,9 @@ module.exports = function run(options) {
     }).then(function() {
         // Deploy in parallel (output gets intermixed though...)
         return Q.all(options.platforms.map(function(platform) {
-            var cmd = path.join(projectRoot, 'platforms', platform, 'cordova', 'run');
-            return superspawn.spawn(cmd, options.options, { printCommand: true, stdio: 'inherit', chmod: true });
+            return platform_lib
+                .getPlatformApi(platform)
+                .run(options.options);
         }));
     }).then(function() {
         return hooksRunner.fire('after_run', options);

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/serve.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/serve.js b/cordova-lib/src/cordova/serve.js
index a187bb8..274f8e0 100644
--- a/cordova-lib/src/cordova/serve.js
+++ b/cordova-lib/src/cordova/serve.js
@@ -63,7 +63,6 @@ function processUrlPath(urlPath, request, response, do302, do404, serveFile) {
     }
 
     var firstSegment = /\/(.*?)\//.exec(urlPath);
-    var parser;
 
     if (!firstSegment) {
         doRoot();
@@ -77,8 +76,9 @@ function processUrlPath(urlPath, request, response, do302, do404, serveFile) {
     // Strip the platform out of the path.
     urlPath = urlPath.slice(platformId.length + 1);
 
+    var platformApi;
     try {
-        parser = platforms.getPlatformProject(platformId, path.join(projectRoot, 'platforms', platformId));
+        platformApi = platforms.getPlatformApi(platformId);
     } catch (e) {
         do404();
         return;
@@ -87,12 +87,12 @@ function processUrlPath(urlPath, request, response, do302, do404, serveFile) {
     var filePath = null;
 
     if (urlPath == '/config.xml') {
-        filePath = parser.config_xml();
+        filePath = platformApi.getPlatformInfo().configXml;
     } else if (urlPath == '/project.json') {
-        processAddRequest(request, response, platformId, projectRoot);
+        processAddRequest(request, response, platformApi);
         return;
     } else if (/^\/www\//.test(urlPath)) {
-        filePath = path.join(parser.www_dir(), urlPath.slice(5));
+        filePath = path.join(platformApi.getPlatformInfo().locations.www, urlPath.slice(5));
     } else if (/^\/+[^\/]*$/.test(urlPath)) {
         do302('/' + platformId + '/www/');
         return;
@@ -125,12 +125,11 @@ function calculateMd5(fileName) {
     return md5sum.digest('hex');
 }
 
-function processAddRequest(request, response, platformId, projectRoot) {
-    var parser = platforms.getPlatformProject(platformId, path.join(projectRoot, 'platforms', platformId));
-    var wwwDir = parser.www_dir();
+function processAddRequest(request, response, platformApi) {
+    var wwwDir = platformApi.getPlatformInfo().locations.www;
     var payload = {
-        'configPath': '/' + platformId + '/config.xml',
-        'wwwPath': '/' + platformId + '/www',
+        'configPath': '/' + platformApi.platform + '/config.xml',
+        'wwwPath': '/' + platformApi.platform + '/www',
         'wwwFileList': shell.find(wwwDir)
             .filter(function(a) { return !fs.statSync(a).isDirectory() && !/(^\.)|(\/\.)/.test(a); })
             .map(function(a) { return {'path': a.slice(wwwDir.length), 'etag': '' + calculateMd5(a)}; })

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/targets.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/targets.js b/cordova-lib/src/cordova/targets.js
index ed0d40b..2f5566e 100644
--- a/cordova-lib/src/cordova/targets.js
+++ b/cordova-lib/src/cordova/targets.js
@@ -49,14 +49,11 @@ module.exports = function targets(options) {
     var projectRoot = cordova_util.cdProjectRoot();
     options = cordova_util.preProcessOptions(options);
 
-    // Remove --list from parameters
-    options.options.splice(options.options.indexOf('--list'), 1);
-
     var result = Q();
     options.platforms.forEach(function(platform) {
-        if (options.options.indexOf('--device') >= 0) {
+        if (options.options.device) {
             result = result.then(displayDevices.bind(null, projectRoot, platform, options.options));
-        } else if(options.options.indexOf('--emulator') >= 0) {
+        } else if(options.options.emulator) {
             result = result.then(displayVirtualDevices.bind(null, projectRoot, platform, options.options));
         } else {
             result = result.then(displayDevices.bind(null, projectRoot, platform, options.options))

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/src/cordova/util.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/util.js b/cordova-lib/src/cordova/util.js
index 0231ec0..c177689 100644
--- a/cordova-lib/src/cordova/util.js
+++ b/cordova-lib/src/cordova/util.js
@@ -233,7 +233,7 @@ function preProcessOptions (inputOptions) {
     }
     result.verbose = result.verbose || false;
     result.platforms = result.platforms || [];
-    result.options = result.options || [];
+    result.options = result.options || {};
 
     var projectRoot = this.isCordova();
 
@@ -248,11 +248,8 @@ function preProcessOptions (inputOptions) {
         result.platforms = projectPlatforms;
     }
 
-    var buildConfigFound = result.options.some(function (option) {
-        return option.indexOf('--buildConfig') === 0;
-    });
-    if (!buildConfigFound && fs.existsSync(path.join(projectRoot, 'build.json'))) {
-        result.options.push('--buildConfig=' + path.join(projectRoot, 'build.json'));
+    if (!result.options.buildConfig && fs.existsSync(path.join(projectRoot, 'build.json'))) {
+        result.options.buildConfig = path.join(projectRoot, 'build.json');
     }
 
     return result;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org


[4/5] cordova-lib git commit: CB-9597 Initial Implementation of PlatformApiPoly

Posted by an...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/install-browserify.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/install-browserify.spec.js b/cordova-lib/spec-plugman/install-browserify.spec.js
deleted file mode 100644
index 6d94eee..0000000
--- a/cordova-lib/spec-plugman/install-browserify.spec.js
+++ /dev/null
@@ -1,519 +0,0 @@
-// /**
-//     Licensed to the Apache Software Foundation (ASF) under one
-//     or more contributor license agreements.  See the NOTICE file
-//     distributed with this work for additional information
-//     regarding copyright ownership.  The ASF licenses this file
-//     to you under the Apache License, Version 2.0 (the
-//     "License"); you may not use this file except in compliance
-//     with the License.  You may obtain a copy of the License at
-
-//     http://www.apache.org/licenses/LICENSE-2.0
-
-//     Unless required by applicable law or agreed to in writing,
-//     software distributed under the License is distributed on an
-//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-//     KIND, either express or implied.  See the License for the
-//     specific language governing permissions and limitations
-//     under the License.
-// */
-
-/* jshint sub:true */
-
-var install = require('../src/plugman/install'),
-    actions = require('../src/plugman/util/action-stack'),
-    PlatformJson = require('../src/plugman/util/PlatformJson'),
-    events  = require('../src/events'),
-    plugman = require('../src/plugman/plugman'),
-    platforms = require('../src/plugman/platforms/common'),
-    common  = require('./common'),
-    fs      = require('fs'),
-    os      = require('os'),
-    path    = require('path'),
-    shell   = require('shelljs'),
-    child_process = require('child_process'),
-    semver  = require('semver'),
-    Q = require('q'),
-    spec    = __dirname,
-    done    = false,
-    srcProject = path.join(spec, 'projects', 'android_install'),
-    temp_dir = path.join(os.tmpdir(), 'plugman-test'),
-    project = path.join(temp_dir, 'android_install'),
-    plugins_dir = path.join(spec, 'plugins'),
-    plugins_install_dir = path.join(project, 'cordova', 'plugins'),
-    plugins = {
-        'org.test.plugins.dummyplugin' : path.join(plugins_dir, 'org.test.plugins.dummyplugin'),
-        'com.cordova.engine' : path.join(plugins_dir, 'com.cordova.engine'),
-        'com.cordova.engine-android' : path.join(plugins_dir, 'com.cordova.engine-android'),
-        'org.test.plugins.childbrowser' : path.join(plugins_dir, 'org.test.plugins.childbrowser'),
-        'com.adobe.vars' : path.join(plugins_dir, 'com.adobe.vars'),
-        'org.test.defaultvariables' : path.join(plugins_dir, 'org.test.defaultvariables'),
-        'A' : path.join(plugins_dir, 'dependencies', 'A'),
-        'B' : path.join(plugins_dir, 'dependencies', 'B'),
-        'C' : path.join(plugins_dir, 'dependencies', 'C'),
-        'F' : path.join(plugins_dir, 'dependencies', 'F'),
-        'G' : path.join(plugins_dir, 'dependencies', 'G')
-    },
-    promise,
-    results = {},
-    dummy_id = 'org.test.plugins.dummyplugin',
-    superspawn = require('../src/cordova/superspawn');
-
-
-// Pre-crete the temp dir, without it the test fails.
-shell.mkdir('-p', temp_dir);
-
-function installPromise(f) {
-  f.then(function(res) { done = true; }, function(err) { done = err; });
-}
-
-var existsSync = fs.existsSync;
-
-// Mocked functions for tests
-var fake = {
-    'existsSync' : {
-        'noPlugins' : function(path){
-            // fake installed plugin directories as 'not found'
-            if( path.slice(-5) !== '.json' && path.indexOf(plugins_install_dir) >= 0) {
-                return false;
-            }
-
-            return existsSync(path);
-        }
-    },
-    'fetch' : {
-        'dependencies' : function(id, dir) {
-            if(id == plugins['A'])
-                return Q(id); // full path to plugin
-
-            return Q( path.join(plugins_dir, 'dependencies', id) );
-        }
-    }
-};
-
-describe('start', function() {
-    var prepare, prepareBrowserify, config_queue_add, proc, actions_push, ca, emit;
-
-    beforeEach(function() {
-        prepare = spyOn(plugman, 'prepare');
-        prepareBrowserify = spyOn(plugman, 'prepareBrowserify');
-        config_queue_add = spyOn(PlatformJson.prototype, 'addInstalledPluginToPrepareQueue');
-        proc = spyOn(actions.prototype, 'process').andReturn( Q(true) );
-        actions_push = spyOn(actions.prototype, 'push');
-        ca = spyOn(actions.prototype, 'createAction');
-    });
-    it('start', function() {
-        shell.rm('-rf', project);
-        shell.cp('-R', path.join(srcProject, '*'), project);
-
-        done = false;
-        promise = Q()
-         .then(
-            function(){ return install('android', project, plugins['org.test.plugins.dummyplugin'], plugins_install_dir, { browserify: true }); }
-        ).then(
-            function(){
-                results['actions_callCount'] = actions_push.callCount;
-                results['actions_create'] = ca.argsForCall[0];
-                results['config_add'] = config_queue_add.argsForCall[0];
-
-                return Q();
-            }
-        ).then(
-            function(){
-                return install('android', project, plugins['com.cordova.engine'], plugins_install_dir, { browserify: true }); }
-        ).then(
-            function(){
-                emit = spyOn(events, 'emit');
-                return install('android', project, plugins['org.test.plugins.childbrowser'], plugins_install_dir, { browserify: true });
-            }
-        ).then(
-            function(){
-                return install('android', project, plugins['com.adobe.vars'], plugins_install_dir, { browserify: true, cli_variables:{API_KEY:'batman'} });
-            }
-        ).then(
-            function(){
-                done = true;
-                results['prepareCount'] = prepareBrowserify.callCount;
-                results['emit_results'] = [];
-
-                for(var i in emit.calls) {
-                    if(emit.calls[i].args[0] === 'results')
-                        results['emit_results'].push(emit.calls[i].args[1]);
-                }
-
-                events.emit('verbose', '***** DONE START *****');
-            }
-        ).fail(
-            function(error) {
-                expect(error).toEqual({});
-            }
-        );
-        waitsFor(function() { return done; }, 'promise never resolved', 2000);
-    });
-});
-
-describe('install', function() {
-    var chmod, exec, add_to_queue, prepare, cp, rm, fetchSpy;
-    var spawnSpy;
-
-    beforeEach(function() {
-        prepare = spyOn(plugman, 'prepare').andReturn( Q(true) );
-        spyOn(plugman, 'prepareBrowserify');
-        exec = spyOn(child_process, 'exec').andCallFake(function(cmd, cb) {
-            cb(false, '', '');
-        });
-        spawnSpy = spyOn(superspawn, 'spawn').andReturn(Q('3.1.0'));
-        spyOn(fs, 'mkdirSync').andReturn(true);
-        spyOn(shell, 'mkdir').andReturn(true);
-        spyOn(platforms, 'copyFile').andReturn(true);
-
-        fetchSpy = spyOn(plugman.raw, 'fetch').andReturn( Q( plugins['com.cordova.engine'] ) );
-        chmod = spyOn(fs, 'chmodSync').andReturn(true);
-        spyOn(fs, 'writeFileSync').andReturn(true);
-        cp = spyOn(shell, 'cp').andReturn(true);
-        rm = spyOn(shell, 'rm').andReturn(true);
-        add_to_queue = spyOn(PlatformJson.prototype, 'addInstalledPluginToPrepareQueue');
-        done = false;
-    });
-
-    describe('success', function() {
-        it('should call prepare after a successful install', function() {
-           expect(results['prepareCount']).toBe(4);
-        });
-
-        it('should emit a results event with platform-agnostic <info>', function() {
-            // org.test.plugins.childbrowser
-            expect(results['emit_results'][0]).toBe('No matter what platform you are installing to, this notice is very important.');
-        });
-        it('should emit a results event with platform-specific <info>', function() {
-            // org.test.plugins.childbrowser
-            expect(results['emit_results'][1]).toBe('Please make sure you read this because it is very important to complete the installation of your plugin.');
-        });
-        it('should interpolate variables into <info> tags', function() {
-            // VariableBrowser
-            expect(results['emit_results'][2]).toBe('Remember that your api key is batman!');
-        });
-
-        it('should call fetch if provided plugin cannot be resolved locally', function() {
-            fetchSpy.andReturn( Q( plugins['org.test.plugins.dummyplugin'] ) );
-            spyOn(fs, 'existsSync').andCallFake( fake['existsSync']['noPlugins'] );
-
-            runs(function() {
-                installPromise(install('android', project, 'CLEANYOURSHORTS', plugins_dir, { browserify: true } ));
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(done).toBe(true);
-                expect(fetchSpy).toHaveBeenCalled();
-            });
-        });
-
-        it('should call the config-changes module\'s add_installed_plugin_to_prepare_queue method after processing an install', function() {
-           expect(results['config_add']).toEqual([dummy_id, {}, true]);
-        });
-        it('should queue up actions as appropriate for that plugin and call process on the action stack',
-           function() {
-                expect(results['actions_callCount']).toEqual(6);
-                expect(results['actions_create']).toEqual([jasmine.any(Function), [jasmine.any(Object), path.join(plugins_install_dir, dummy_id), dummy_id, jasmine.any(Object)], jasmine.any(Function), [jasmine.any(Object), dummy_id, jasmine.any(Object)]]);
-        });
-
-        it('should check version if plugin has engine tag', function(){
-            var satisfies = spyOn(semver, 'satisfies').andReturn(true);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '2.5.0\n');
-            });
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(satisfies).toHaveBeenCalledWith('2.5.0','>=2.3.0');
-            });
-        });
-        it('should check version and munge it a little if it has "rc" in it so it plays nice with semver (introduce a dash in it)', function() {
-            var satisfies = spyOn(semver, 'satisfies').andReturn(true);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '3.0.0rc1\n');
-            });
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(satisfies).toHaveBeenCalledWith('3.0.0-rc1','>=2.3.0');
-            });
-        });
-        it('should check specific platform version over cordova version if specified', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '3.1.0\n');
-            });
-            fetchSpy.andReturn( Q( plugins['com.cordova.engine-android'] ) );
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine-android'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(spy).toHaveBeenCalledWith('3.1.0','>=3.1.0');
-            });
-        });
-        it('should check platform sdk version if specified', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            fetchSpy.andReturn( Q( plugins['com.cordova.engine-android'] ) );
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '18\n');
-            });
-
-            runs(function() {
-                installPromise( install('android', project, 'com.cordova.engine-android', plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                // <engine name="cordova" VERSION=">=3.0.0"/>
-                // <engine name="cordova-android" VERSION=">=3.1.0"/>
-                // <engine name="android-sdk" VERSION=">=18"/>
-
-                expect(spy.calls.length).toBe(3);
-                expect(spy.calls[0].args).toEqual([ '18.0.0', '>=3.0.0' ]);
-                expect(spy.calls[1].args).toEqual([ '18.0.0', '>=3.1.0' ]);
-                expect(spy.calls[2].args).toEqual([ '18.0.0','>=18' ]);
-            });
-        });
-        it('should check engine versions', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            fetchSpy.andReturn( Q( plugins['com.cordova.engine'] ) );
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                // <engine name="cordova" version=">=2.3.0"/>
-                // <engine name="cordova-plugman" version=">=0.10.0" />
-                // <engine name="mega-fun-plugin" version=">=1.0.0" scriptSrc="megaFunVersion" platform="*" />
-                // <engine name="mega-boring-plugin" version=">=3.0.0" scriptSrc="megaBoringVersion" platform="ios|android" />
-
-                var plugmanVersion = require('../package.json').version;
-                plugmanVersion = plugmanVersion.replace(/-dev$/, '');
-
-                expect(spy.calls.length).toBe(4);
-                expect(spy.calls[0].args).toEqual([ null, '>=2.3.0' ]);
-                expect(spy.calls[1].args).toEqual([ plugmanVersion, '>=0.10.0' ]);
-                expect(spy.calls[2].args).toEqual([ null, '>=1.0.0' ]);
-                expect(spy.calls[3].args).toEqual([ null, '>=3.0.0' ]);
-            });
-        });
-        it('should not check custom engine version that is not supported for platform', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            runs(function() {
-                installPromise( install('blackberry10', project, plugins['com.cordova.engine'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(spy).not.toHaveBeenCalledWith('','>=3.0.0');
-            });
-        });
-
-        describe('with dependencies', function() {
-            var emit;
-            beforeEach(function() {
-                spyOn(fs, 'existsSync').andCallFake( fake['existsSync']['noPlugins'] );
-                fetchSpy.andCallFake( fake['fetch']['dependencies'] );
-                emit = spyOn(events, 'emit');
-                exec.andCallFake(function(cmd, cb) {
-                    cb(null, '9.0.0\n');
-                });
-            });
-
-            it('should install any dependent plugins if missing', function() {
-                runs(function() {
-                    installPromise( install('android', project, plugins['A'], plugins_install_dir, { browserify: true }) );
-                });
-                waitsFor(function() { return done; }, 'install promise never resolved', 200);
-                runs(function() {
-                    // Look for 'Installing plugin ...' in events
-                    var install = common.spy.getInstall(emit);
-
-                    expect(install).toEqual([
-                        'Install start for "C" on android.',
-                        'Install start for "D" on android.',
-                        'Install start for "A" on android.'
-                    ]);
-                });
-            });
-
-            it('should install any dependent plugins from registry when url is not defined', function() {
-                // Plugin A depends on C & D
-                runs(function() {
-                    installPromise( install('android', project, plugins['A'], plugins_install_dir, { browserify: true }) );
-                });
-                waitsFor(function() { return done; }, 'promise never resolved', 200);
-                runs(function() {
-                    // TODO: this is same test as above? Need test other dependency with url=?
-                    var install = common.spy.getInstall(emit);
-
-                    expect(install).toEqual([
-                        'Install start for "C" on android.',
-                        'Install start for "D" on android.',
-                        'Install start for "A" on android.'
-                    ]);
-                });
-            });
-
-            it('should process all dependent plugins with alternate routes to the same plugin', function() {
-                // Plugin F depends on A, C, D and E
-                runs(function () {
-                    installPromise(install('android', project, plugins['F'], plugins_install_dir, { browserify: true }));
-                });
-                waitsFor(function () { return done; }, 'install promise never resolved', 200);
-                runs(function () {
-                    var install = common.spy.getInstall(emit);
-
-                    expect(install).toEqual([
-                        'Install start for "C" on android.',
-                        'Install start for "D" on android.',
-                        'Install start for "A" on android.',
-                        'Install start for "D" on android.',
-                        'Install start for "F" on android.'
-                    ]);
-                });
-            });
-
-            it('should throw if there is a cyclic dependency', function() {
-                runs(function () {
-                    installPromise( install('android', project, plugins['G'], plugins_install_dir, { browserify: true }) );
-                });
-                waitsFor(function () { return done; }, 'install promise never resolved', 200);
-                runs(function () {
-                    common.spy.getInstall(emit);
-
-                    expect(done.message).toEqual('Cyclic dependency from G to H');
-                });
-            });
-
-            it('install subdir relative to top level plugin if no fetch meta', function() {
-                runs(function () {
-                    installPromise(install('android', project, plugins['B'], plugins_install_dir, { browserify: true }));
-                });
-                waitsFor(function () { return done; }, 'install promise never resolved', 200);
-                runs(function () {
-                    var install = common.spy.getInstall(emit);
-
-                    expect(install).toEqual([
-                        'Install start for "D" on android.',
-                        'Install start for "E" on android.',
-                        'Install start for "B" on android.'
-                    ]);
-                });
-            });
-
-            it('install uses meta data (if available) of top level plugin source', function() {
-                // Fake metadata so plugin 'B' appears from 'meta/B'
-                var meta = require('../src/plugman/util/metadata');
-                spyOn(meta, 'get_fetch_metadata').andCallFake(function(){
-                    return {
-                        source: {type: 'dir', url: path.join(plugins['B'], '..', 'meta')}
-                    };
-                });
-
-                runs(function () {
-                    installPromise(install('android', project, plugins['B'], plugins_install_dir, { browserify: true }));
-                });
-                waitsFor(function () { return done; }, 'install promise never resolved', 200);
-                runs(function () {
-                    var install = common.spy.getInstall(emit);
-
-                    expect(install).toEqual([
-                        'Install start for "D" on android.',
-                        'Install start for "E" on android.',
-                        'Install start for "B" on android.'
-                    ]);
-
-                    var copy = common.spy.startsWith(emit, 'Copying from');
-                    expect(copy.length).toBe(3);
-                    expect(copy[0].indexOf(path.normalize('meta/D')) > 0).toBe(true);
-                    expect(copy[1].indexOf(path.normalize('meta/subdir/E')) > 0).toBe(true);
-                });
-            });
-        });
-
-    });
-
-    describe('failure', function() {
-        it('should throw if platform is unrecognized', function() {
-            runs(function() {
-                installPromise( install('atari', project, 'SomePlugin', plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toContain('atari not supported.');
-            });
-        });
-        it('should throw if variables are missing', function() {
-            runs(function() {
-                installPromise( install('android', project, plugins['com.adobe.vars'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function(){ return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toContain('Variable(s) missing: API_KEY');
-            });
-        });
-         it('should not throw exception on default variables', function() {
-            runs(function() {
-                installPromise( install('android', project, plugins['org.test.defaultvariables'], plugins_install_dir, { browserify: true , cli_variables:{API_KEY:'del7a' }}) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toEqual('true');
-            });
-        });
-        it('should throw if git is not found on the path and a remote url is requested', function() {
-            spyOn(fs, 'existsSync').andCallFake( fake['existsSync']['noPlugins'] );
-            fetchSpy.andCallThrough();
-            spyOn(shell, 'which').andReturn(null);
-            runs(function() {
-                installPromise( install('android', project, 'https://git-wip-us.apache.org/repos/asf/cordova-plugin-camera.git', plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function(){ return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toContain('"git" command line tool is not installed: make sure it is accessible on your PATH.');
-            });
-        });
-        it('should not fail if plugin version is less than the minimum requirement. Instead skip.', function(){
-            spyOn(semver, 'satisfies').andReturn(false);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '0.0.1\n');
-            });
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function(){ return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toMatch(true);
-            });
-        });
-    });
-
-});
-
-// When run using 'npm test', the removal of temp_dir is causing
-// tests in 'install.spec.js' to fail.
-
-// describe('end', function() {
-
-//     it('end', function() {
-//         done = false;
-
-//         promise.fin(function(err){
-//             if(err)
-//                 events.emit('error', err);
-
-//             shell.rm('-rf', temp_dir);
-//             done = true;
-//         });
-
-//         waitsFor(function() { return done; }, 'promise never resolved', 500);
-//     });
-// });

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/install.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/install.spec.js b/cordova-lib/spec-plugman/install.spec.js
index c50e907..6da5767 100644
--- a/cordova-lib/spec-plugman/install.spec.js
+++ b/cordova-lib/spec-plugman/install.spec.js
@@ -21,6 +21,8 @@
 
 var install = require('../src/plugman/install'),
     actions = require('../src/plugman/util/action-stack'),
+    xmlHelpers = require('../src/util/xml-helpers'),
+    et      = require('elementtree'),
     PlatformJson = require('../src/plugman/util/PlatformJson'),
     events  = require('../src/events'),
     plugman = require('../src/plugman/plugman'),
@@ -55,7 +57,6 @@ var install = require('../src/plugman/install'),
     },
     promise,
     results = {},
-    dummy_id = 'org.test.plugins.dummyplugin',
     superspawn = require('../src/cordova/superspawn');
 
 
@@ -90,15 +91,38 @@ var fake = {
     }
 };
 
+var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
+    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
+    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
+    '        id        = "io.cordova.hellocordova"\n' +
+    '        version   = "0.0.1">\n' +
+    '    <name>Hello Cordova</name>\n' +
+    '    <description>\n' +
+    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
+    '    </description>\n' +
+    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
+    '        Apache Cordova Team\n' +
+    '    </author>\n' +
+    '    <content src="index.html" />\n' +
+    '    <access origin="*" />\n' +
+    '    <preference name="fullscreen" value="true" />\n' +
+    '    <preference name="webviewbounce" value="true" />\n' +
+    '</widget>\n';
+
 describe('start', function() {
-    var prepare, config_queue_add, proc, actions_push, ca, emit;
+    var config_queue_add, proc, actions_push, ca, emit;
 
     beforeEach(function() {
-        prepare = spyOn(plugman, 'prepare');
         config_queue_add = spyOn(PlatformJson.prototype, 'addInstalledPluginToPrepareQueue');
         proc = spyOn(actions.prototype, 'process').andReturn( Q(true) );
         actions_push = spyOn(actions.prototype, 'push');
         ca = spyOn(actions.prototype, 'createAction');
+
+        var origParseElementtreeSync = xmlHelpers.parseElementtreeSync.bind(xmlHelpers);
+        spyOn(xmlHelpers, 'parseElementtreeSync').andCallFake(function(path) {
+            if (/config.xml$/.test(path)) return new et.ElementTree(et.XML(TEST_XML));
+            return origParseElementtreeSync(path);
+        });
     });
     it('start', function() {
         shell.rm('-rf', project);
@@ -107,7 +131,9 @@ describe('start', function() {
         done = false;
         promise = Q()
          .then(
-            function(){ return install('android', project, plugins['org.test.plugins.dummyplugin']); }
+            function(){
+                return install('android', project, plugins['org.test.plugins.dummyplugin']);
+            }
         ).then(
             function(){
                 results['actions_callCount'] = actions_push.callCount;
@@ -134,7 +160,6 @@ describe('start', function() {
         ).then(
             function(){
                 done = true;
-                results['prepareCount'] = prepare.callCount;
                 results['emit_results'] = [];
 
                 for(var i in emit.calls) {
@@ -154,11 +179,10 @@ describe('start', function() {
 });
 
 describe('install', function() {
-    var chmod, exec, add_to_queue, prepare, cp, rm, fetchSpy;
+    var chmod, exec, add_to_queue, cp, rm, fetchSpy;
     var spawnSpy;
 
     beforeEach(function() {
-        prepare = spyOn(plugman, 'prepare').andReturn( Q(true) );
 
         exec = spyOn(child_process, 'exec').andCallFake(function(cmd, cb) {
             cb(false, '', '');
@@ -178,10 +202,6 @@ describe('install', function() {
     });
 
     describe('success', function() {
-        it('should call prepare after a successful install', function() {
-           expect(results['prepareCount']).toBe(5);
-        });
-
         it('should emit a results event with platform-agnostic <info>', function() {
             // org.test.plugins.childbrowser
             expect(results['emit_results'][0]).toBe('No matter what platform you are installing to, this notice is very important.');
@@ -194,7 +214,6 @@ describe('install', function() {
             // VariableBrowser
             expect(results['emit_results'][2]).toBe('Remember that your api key is batman!');
         });
-
         it('should call fetch if provided plugin cannot be resolved locally', function() {
             fetchSpy.andReturn( Q( plugins['org.test.plugins.dummyplugin'] ) );
             spyOn(fs, 'existsSync').andCallFake( fake['existsSync']['noPlugins'] );
@@ -209,102 +228,80 @@ describe('install', function() {
             });
         });
 
-        it('should call the config-changes module\'s add_installed_plugin_to_prepare_queue method after processing an install', function() {
-           expect(results['config_add']).toEqual([dummy_id, {}, true]);
-        });
-        it('should queue up actions as appropriate for that plugin and call process on the action stack',
-           function() {
-                expect(results['actions_callCount']).toEqual(6);
-                expect(results['actions_create']).toEqual([jasmine.any(Function), [jasmine.any(Object), path.join(plugins_install_dir, dummy_id), dummy_id, jasmine.any(Object)], jasmine.any(Function), [jasmine.any(Object), dummy_id, jasmine.any(Object)]]);
-        });
-
-        it('should check version if plugin has engine tag', function(){
-            var satisfies = spyOn(semver, 'satisfies').andReturn(true);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '2.5.0\n');
-            });
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine']) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(satisfies).toHaveBeenCalledWith('2.5.0','>=2.3.0');
-            });
-        });
-        it('should check version and munge it a little if it has "rc" in it so it plays nice with semver (introduce a dash in it)', function() {
-            var satisfies = spyOn(semver, 'satisfies').andReturn(true);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '3.0.0rc1\n');
-            });
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine']) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(satisfies).toHaveBeenCalledWith('3.0.0-rc1','>=2.3.0');
-            });
-        });
-        it('should check specific platform version over cordova version if specified', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '3.1.0\n');
-            });
-            fetchSpy.andReturn( Q( plugins['com.cordova.engine-android'] ) );
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine-android']) );
-            });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(spy).toHaveBeenCalledWith('3.1.0','>=3.1.0');
+        describe('engine versions', function () {
+            var fail, satisfies;
+            beforeEach(function () {
+                fail = jasmine.createSpy('fail');
+                satisfies = spyOn(semver, 'satisfies').andReturn(true);
+                spyOn(PlatformJson.prototype, 'isPluginInstalled').andReturn(false);
+            });
+
+            it('should check version if plugin has engine tag', function(done){
+                exec.andCallFake(function(cmd, cb) { cb(null, '2.5.0\n'); });
+                install('android', project, plugins['com.cordova.engine'])
+                .fail(fail)
+                .fin(function () {
+                    expect(satisfies).toHaveBeenCalledWith('2.5.0','>=2.3.0');
+                    done();
+                });
             });
-        });
-        it('should check platform sdk version if specified', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            fetchSpy.andReturn( Q( plugins['com.cordova.engine-android'] ) );
-            exec.andCallFake(function(cmd, cb) {
-                cb(null, '18\n');
+            it('should check version and munge it a little if it has "rc" in it so it plays nice with semver (introduce a dash in it)', function(done) {
+                exec.andCallFake(function(cmd, cb) { cb(null, '3.0.0rc1\n'); });
+                install('android', project, plugins['com.cordova.engine'])
+                .fail(fail)
+                .fin(function () {
+                    expect(satisfies).toHaveBeenCalledWith('3.0.0-rc1','>=2.3.0');
+                    done();
+                });
             });
-
-            runs(function() {
-                installPromise( install('android', project, 'com.cordova.engine-android') );
+            it('should check specific platform version over cordova version if specified', function(done) {
+                exec.andCallFake(function(cmd, cb) { cb(null, '3.1.0\n'); });
+                install('android', project, plugins['com.cordova.engine-android'])
+                .fail(fail)
+                .fin(function() {
+                    expect(satisfies).toHaveBeenCalledWith('3.1.0','>=3.1.0');
+                    done();
+                });
             });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                // <engine name="cordova" VERSION=">=3.0.0"/>
-                // <engine name="cordova-android" VERSION=">=3.1.0"/>
-                // <engine name="android-sdk" VERSION=">=18"/>
-
-                expect(spy.calls.length).toBe(3);
-                expect(spy.calls[0].args).toEqual([ '18.0.0', '>=3.0.0' ]);
-                expect(spy.calls[1].args).toEqual([ '18.0.0', '>=3.1.0' ]);
-                expect(spy.calls[2].args).toEqual([ '18.0.0','>=18' ]);
+            it('should check platform sdk version if specified', function(done) {
+                exec.andCallFake(function(cmd, cb) { cb(null, '18\n'); });
+                install('android', project, plugins['com.cordova.engine-android'])
+                .fail(fail)
+                .fin(function() {
+                    expect(satisfies.calls.length).toBe(3);
+                    // <engine name="cordova" VERSION=">=3.0.0"/>
+                    expect(satisfies.calls[0].args).toEqual([ '18.0.0', '>=3.0.0' ]);
+                    // <engine name="cordova-android" VERSION=">=3.1.0"/>
+                    expect(satisfies.calls[1].args).toEqual([ '18.0.0', '>=3.1.0' ]);
+                    // <engine name="android-sdk" VERSION=">=18"/>
+                    expect(satisfies.calls[2].args).toEqual([ '18.0.0','>=18' ]);
+                    done();
+                });
             });
-        });
-        it('should check engine versions', function() {
-            var spy = spyOn(semver, 'satisfies').andReturn(true);
-            fetchSpy.andReturn( Q( plugins['com.cordova.engine'] ) );
-
-            runs(function() {
-                installPromise( install('android', project, plugins['com.cordova.engine']) );
+            it('should check engine versions', function(done) {
+                install('android', project, plugins['com.cordova.engine'])
+                .fail(fail)
+                .fin(function() {
+                    var plugmanVersion = require('../package.json').version.replace('-dev', '');
+                    expect(satisfies.calls.length).toBe(4);
+                    // <engine name="cordova" version=">=2.3.0"/>
+                    expect(satisfies.calls[0].args).toEqual([ null, '>=2.3.0' ]);
+                    // <engine name="cordova-plugman" version=">=0.10.0" />
+                    expect(satisfies.calls[1].args).toEqual([ plugmanVersion, '>=0.10.0' ]);
+                    // <engine name="mega-fun-plugin" version=">=1.0.0" scriptSrc="megaFunVersion" platform="*" />
+                    expect(satisfies.calls[2].args).toEqual([ null, '>=1.0.0' ]);
+                    // <engine name="mega-boring-plugin" version=">=3.0.0" scriptSrc="megaBoringVersion" platform="ios|android" />
+                    expect(satisfies.calls[3].args).toEqual([ null, '>=3.0.0' ]);
+                    done();
+                });
             });
-            waitsFor(function() { return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                // <engine name="cordova" version=">=2.3.0"/>
-                // <engine name="cordova-plugman" version=">=0.10.0" />
-                // <engine name="mega-fun-plugin" version=">=1.0.0" scriptSrc="megaFunVersion" platform="*" />
-                // <engine name="mega-boring-plugin" version=">=3.0.0" scriptSrc="megaBoringVersion" platform="ios|android" />
-
-                var plugmanVersion = require('../package.json').version;
-                plugmanVersion = plugmanVersion.replace(/-dev$/, '');
-
-                expect(spy.calls.length).toBe(4);
-                expect(spy.calls[0].args).toEqual([ null, '>=2.3.0' ]);
-                expect(spy.calls[1].args).toEqual([ plugmanVersion, '>=0.10.0' ]);
-                expect(spy.calls[2].args).toEqual([ null, '>=1.0.0' ]);
-                expect(spy.calls[3].args).toEqual([ null, '>=3.0.0' ]);
+            it('should not check custom engine version that is not supported for platform', function(done) {
+                install('blackberry10', project, plugins['com.cordova.engine'])
+                .then(fail)
+                .fail(function () {
+                    expect(satisfies).not.toHaveBeenCalledWith('','>=3.0.0');
+                })
+                .fin(done);
             });
         });
         it('should not check custom engine version that is not supported for platform', function() {
@@ -440,7 +437,6 @@ describe('install', function() {
                 });
             });
         });
-
     });
 
     describe('failure', function() {
@@ -453,13 +449,17 @@ describe('install', function() {
                 expect(''+done).toContain('atari not supported.');
             });
         });
-        it('should throw if variables are missing', function() {
-            runs(function() {
-                installPromise( install('android', project, plugins['com.adobe.vars']) );
-            });
-            waitsFor(function(){ return done; }, 'install promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toContain('Variable(s) missing: API_KEY');
+        it('should throw if variables are missing', function(done) {
+            var success = jasmine.createSpy('success');
+            spyOn(PlatformJson.prototype, 'isPluginInstalled').andReturn(false);
+            install('android', project, plugins['com.adobe.vars'])
+            .then(success)
+            .fail(function (err) {
+                expect(err).toContain('Variable(s) missing: API_KEY');
+            })
+            .fin(function () {
+                expect(success).not.toHaveBeenCalled();
+                done();
             });
         });
         it('should throw if git is not found on the path and a remote url is requested', function() {
@@ -488,10 +488,8 @@ describe('install', function() {
             });
         });
     });
-
 });
 
-
 describe('end', function() {
 
     it('end', function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/prepare.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/prepare.spec.js b/cordova-lib/spec-plugman/prepare.spec.js
deleted file mode 100644
index c3ff707..0000000
--- a/cordova-lib/spec-plugman/prepare.spec.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-var prepare = require('../src/plugman/prepare'),
-    common  = require('../src/plugman/platforms/common'),
-    fs      = require('fs'),
-    path    = require('path'),
-    shell   = require('shelljs'),
-    config_changes = require('../src/plugman/util/config-changes'),
-    PlatformJson = require('../src/plugman/util/PlatformJson'),
-    temp    = __dirname,
-    plugins_dir = path.join(temp, 'plugins');
-
-describe('prepare', function() {
-    var proc, platform_json, write, mkdir, rm;
-    beforeEach(function() {
-        rm = spyOn(shell, 'rm');
-        mkdir = spyOn(shell, 'mkdir');
-        proc = spyOn(config_changes, 'process');
-        platform_json = spyOn(PlatformJson, 'load').andReturn(new PlatformJson(null, null, {installed_plugins:{},dependent_plugins:{},prepare_queue:{uninstalled:[]}}));
-        write = spyOn(fs, 'writeFileSync');
-    });
-    it('should create cordova_plugins.js file in a custom www directory', function() {
-        var custom_www = path.join(temp, 'assets', 'custom_www'),
-            js = path.join(temp, 'assets', 'custom_www', 'cordova_plugins.js');
-        prepare(temp, 'android', plugins_dir, custom_www);
-        expect(write).toHaveBeenCalledWith(js, jasmine.any(String), 'utf-8');
-    });
-    describe('handling of js-modules', function() {
-        var copySpy;
-        beforeEach(function() {
-            copySpy = spyOn(common, 'copyFile');
-            platform_json.andReturn(new PlatformJson(null, null, {
-                installed_plugins: {plugin_one: '', plugin_two: ''},
-                dependent_plugins: {}, prepare_queue: {uninstalled:[]}
-            }));
-        });
-        describe('uninstallation/removal', function() {
-            var existsSync;
-            beforeEach(function() {
-                existsSync = spyOn(fs, 'existsSync').andReturn(true);
-                platform_json.andReturn(new PlatformJson(null, null, {installed_plugins:{},dependent_plugins:{},prepare_queue:{uninstalled:[{
-                    plugin:'nickelback',
-                    id:'nickelback',
-                    topLevel:true
-                }]}}));
-            });
-            it('should remove any www/plugins directories related to plugins being queued for removal', function() {
-                prepare(temp, 'android', plugins_dir);
-                expect(rm).toHaveBeenCalledWith('-rf', path.join(temp, 'assets', 'www', 'plugins', 'nickelback'));
-            });
-        });
-    });
-    it('should call into config-changes\' process method to do config processing', function() {
-        prepare(temp, 'android', plugins_dir);
-        expect(proc).toHaveBeenCalledWith(plugins_dir, temp, 'android', jasmine.any(Object), jasmine.any(Object));
-    });
-});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/projects/wp8/config.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/wp8/config.xml b/cordova-lib/spec-plugman/projects/wp8/config.xml
new file mode 100644
index 0000000..c08b522
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/wp8/config.xml
@@ -0,0 +1,12 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget id="io.cordova.cordovaapp" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
+    <name>CordovaApp</name>
+    <description>
+        A sample Apache Cordova application that responds to the deviceready event.
+    </description>
+    <author email="dev@cordova.apache.org" href="http://cordova.io">
+        Apache Cordova Team
+    </author>
+    <content src="index.html" />
+    <access origin="*" />
+</widget>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/uninstall-browserify.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/uninstall-browserify.spec.js b/cordova-lib/spec-plugman/uninstall-browserify.spec.js
deleted file mode 100644
index d68b0a6..0000000
--- a/cordova-lib/spec-plugman/uninstall-browserify.spec.js
+++ /dev/null
@@ -1,315 +0,0 @@
-/**
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-/* jshint sub:true */
-
-var uninstall = require('../src/plugman/uninstall'),
-    install = require('../src/plugman/install'),
-    actions = require('../src/plugman/util/action-stack'),
-    PlatformJson = require('../src/plugman/util/PlatformJson'),
-    events  = require('../src/events'),
-    plugman = require('../src/plugman/plugman'),
-    common  = require('./common'),
-    fs      = require('fs'),
-    path    = require('path'),
-    shell   = require('shelljs'),
-    Q       = require('q'),
-    spec    = __dirname,
-    done    = false,
-    srcProject = path.join(spec, 'projects', 'android_uninstall'),
-    project = path.join(spec, 'projects', 'android_uninstall.test'),
-    project2 = path.join(spec, 'projects', 'android_uninstall.test2'),
-
-    plugins_dir = path.join(spec, 'plugins'),
-    plugins_install_dir = path.join(project, 'cordova', 'plugins'),
-    plugins_install_dir2 = path.join(project2, 'cordova', 'plugins'),
-
-    plugins = {
-        'org.test.plugins.dummyplugin' : path.join(plugins_dir, 'org.test.plugins.dummyplugin'),
-        'A' : path.join(plugins_dir, 'dependencies', 'A'),
-        'C' : path.join(plugins_dir, 'dependencies', 'C')
-    },
-    promise,
-    dummy_id = 'org.test.plugins.dummyplugin';
-
-function uninstallPromise(f) {
-    return f.then(function() { done = true; }, function(err) { done = err; });
-}
-
-describe('start', function() {
-
-    it('start', function() {
-        shell.rm('-rf', project);
-        shell.rm('-rf', project2);
-        shell.cp('-R', path.join(srcProject, '*'), project);
-        shell.cp('-R', path.join(srcProject, '*'), project2);
-
-        done = false;
-        promise = Q()
-        .then(
-            function(){ return install('android', project, plugins['org.test.plugins.dummyplugin'], plugins_install_dir, { browserify: true }); }
-        ).then(
-            function(){ return install('android', project, plugins['A'], plugins_install_dir, { browserify: true }); }
-        ).then(
-            function(){ return install('android', project2, plugins['C'], plugins_install_dir2, { browserify: true }); }
-        ).then(
-            function(){ return install('android', project2, plugins['A'], plugins_install_dir2, { browserify: true }); }
-        ).then(
-            function(){ done = true; }
-        ).fail(function(err) {
-            done = err.stack;
-        });
-        waitsFor(function() { return done; }, 'promise never resolved', 5000);
-        runs(function() {
-            expect(done).toBe(true);
-        });
-    });
-});
-
-describe('uninstallPlatform', function() {
-    var proc, prepare, prepareBrowserify, actions_push, add_to_queue, c_a, rm;
-    var fsWrite;
-
-    beforeEach(function() {
-        proc = spyOn(actions.prototype, 'process').andReturn(Q());
-        actions_push = spyOn(actions.prototype, 'push');
-        c_a = spyOn(actions.prototype, 'createAction');
-        prepare = spyOn(plugman, 'prepare');
-        prepareBrowserify = spyOn(plugman, 'prepareBrowserify');
-        fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
-        rm = spyOn(shell, 'rm').andReturn(true);
-        spyOn(shell, 'cp').andReturn(true);
-        add_to_queue = spyOn(PlatformJson.prototype, 'addUninstalledPluginToPrepareQueue');
-        done = false;
-    });
-    describe('success', function() {
-        it('should call prepare after a successful uninstall', function() {
-            runs(function() {
-                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id, plugins_install_dir, { browserify: true }));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(prepareBrowserify).toHaveBeenCalled();
-            });
-        });
-        it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method after processing an install', function() {
-            runs(function() {
-                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id, plugins_install_dir, { browserify: true }));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(add_to_queue).toHaveBeenCalledWith(dummy_id, true);
-            });
-        });
-        it('should queue up actions as appropriate for that plugin and call process on the action stack', function() {
-            runs(function() {
-                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id, plugins_install_dir, { browserify: true }));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(actions_push.calls.length).toEqual(6);
-                expect(proc).toHaveBeenCalled();
-            });
-        });
-
-        describe('with dependencies', function() {
-            var emit;
-            beforeEach(function() {
-                emit = spyOn(events, 'emit');
-            });
-            it('should uninstall "dangling" dependencies', function() {
-                runs(function() {
-                    uninstallPromise(uninstall.uninstallPlatform('android', project, 'A', plugins_install_dir, { browserify: true }));
-                });
-                waitsFor(function() { return done; }, 'promise never resolved', 200);
-                runs(function() {
-                    expect(emit).toHaveBeenCalledWith('log', 'Uninstalling 2 dependent plugins.');
-                });
-            });
-        });
-    });
-
-    describe('failure', function() {
-        it('should throw if platform is unrecognized', function() {
-            runs(function() {
-                uninstallPromise( uninstall.uninstallPlatform('atari', project, 'SomePlugin', plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toContain('atari not supported.');
-            });
-        });
-        it('should throw if plugin is missing', function() {
-            runs(function() {
-                uninstallPromise( uninstall.uninstallPlatform('android', project, 'SomePluginThatDoesntExist', plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(''+done).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
-            });
-        });
-    });
-});
-
-describe('uninstallPlugin', function() {
-    var rm, fsWrite, rmstack = [], emit;
-
-    beforeEach(function() {
-        fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
-        rm = spyOn(shell, 'rm').andCallFake(function(f,p) { rmstack.push(p); return true; });
-        rmstack = [];
-        emit = spyOn(events, 'emit');
-        done = false;
-    });
-    describe('with dependencies', function() {
-
-        it('should delete all dependent plugins', function() {
-            runs(function() {
-                uninstallPromise( uninstall.uninstallPlugin('A', plugins_install_dir, {browserify: true}) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                var del = common.spy.getDeleted(emit);
-
-                expect(del).toEqual([
-                    'Deleted "C"',
-                    'Deleted "D"',
-                    'Deleted "A"'
-                ]);
-            });
-        });
-
-        it('should fail if plugin is a required dependency', function() {
-            runs(function() {
-                uninstallPromise( uninstall.uninstallPlugin('C', plugins_install_dir, {browserify: true}) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(done.message).toBe('"C" is required by (A) and cannot be removed (hint: use -f or --force)');
-            });
-        });
-
-        it('allow forcefully removing a plugin', function() {
-            runs(function() {
-                uninstallPromise( uninstall.uninstallPlugin('C', plugins_install_dir, {browserify: true, force: true}) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(done).toBe(true);
-                var del = common.spy.getDeleted(emit);
-                expect(del).toEqual(['Deleted "C"']);
-            });
-        });
-
-        it('never remove top level plugins if they are a dependency', function() {
-            runs(function() {
-                uninstallPromise( uninstall.uninstallPlugin('A', plugins_install_dir2, {browserify: true}) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                var del = common.spy.getDeleted(emit);
-
-                expect(del).toEqual([
-                    'Deleted "D"',
-                    'Deleted "A"'
-                ]);
-            });
-        });
-    });
-});
-
-describe('uninstall', function() {
-    var fsWrite, rm, add_to_queue;
-
-    beforeEach(function() {
-        fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
-        rm = spyOn(shell, 'rm').andReturn(true);
-        add_to_queue = spyOn(PlatformJson.prototype, 'addUninstalledPluginToPrepareQueue');
-        done = false;
-    });
-    describe('success', function() {
-        it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method after processing an install', function() {
-            runs(function() {
-                uninstallPromise( uninstall('android', project, plugins['org.test.plugins.dummyplugin'], plugins_install_dir, { browserify: true }) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 500);
-            runs(function() {
-                expect(add_to_queue).toHaveBeenCalledWith(dummy_id, true);
-            });
-        });
-    });
-
-    describe('failure', function() {
-        it('should throw if platform is unrecognized', function() {
-            runs(function() {
-                uninstallPromise(uninstall('atari', project, 'SomePlugin', plugins_install_dir, { browserify: true }));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 500);
-            runs(function() {
-                expect(''+done).toContain('atari not supported.');
-            });
-        });
-        it('should throw if plugin is missing', function() {
-            runs(function() {
-                uninstallPromise(uninstall('android', project, 'SomePluginThatDoesntExist', plugins_install_dir, { browserify: true }));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 500);
-            runs(function() {
-                expect(''+done).toContain('Plugin "SomePluginThatDoesntExist" not found. Already uninstalled?');
-            });
-        });
-    });
-});
-
-describe('end', function() {
-
-    it('end', function() {
-        done = false;
-
-        promise.then(
-            function(){
-                return uninstall('android', project, plugins['org.test.plugins.dummyplugin'], plugins_install_dir, { browserify: true });
-            }
-        ).then(
-            function(){
-                // Fails... A depends on
-                return uninstall('android', project, plugins['C'], plugins_install_dir, { browserify: true });
-            }
-        ).fail(
-            function(err) {
-                expect(err.message).toBe('The plugin \'C\' is required by (A), skipping uninstallation.');
-            }
-        ).then(
-            function(){
-                // dependencies on C,D ... should this only work with --recursive? prompt user..?
-                return uninstall('android', project, plugins['A'], plugins_install_dir, { browserify: true });
-            }
-        ).fin(function(err){
-            if(err)
-                plugman.emit('error', err);
-
-            shell.rm('-rf', project);
-            shell.rm('-rf', project2);
-            done = true;
-        });
-
-        waitsFor(function() { return done; }, 'promise never resolved', 1500);
-    });
-});
-

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/uninstall.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/uninstall.spec.js b/cordova-lib/spec-plugman/uninstall.spec.js
index 264d8b9..63f1ff2 100644
--- a/cordova-lib/spec-plugman/uninstall.spec.js
+++ b/cordova-lib/spec-plugman/uninstall.spec.js
@@ -22,10 +22,12 @@
 var uninstall = require('../src/plugman/uninstall'),
     install = require('../src/plugman/install'),
     actions = require('../src/plugman/util/action-stack'),
-    PlatformJson = require('../src/plugman/util/PlatformJson'),
     events  = require('../src/events'),
     plugman = require('../src/plugman/plugman'),
     common  = require('./common'),
+    platforms = require('../src/platforms/platforms'),
+    xmlHelpers = require('../src/util/xml-helpers'),
+    et      = require('elementtree'),
     fs      = require('fs'),
     path    = require('path'),
     shell   = require('shelljs'),
@@ -50,11 +52,34 @@ var uninstall = require('../src/plugman/uninstall'),
     promise,
     dummy_id = 'org.test.plugins.dummyplugin';
 
+var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
+    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
+    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
+    '        id        = "io.cordova.hellocordova"\n' +
+    '        version   = "0.0.1">\n' +
+    '    <name>Hello Cordova</name>\n' +
+    '    <description>\n' +
+    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
+    '    </description>\n' +
+    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
+    '        Apache Cordova Team\n' +
+    '    </author>\n' +
+    '    <content src="index.html" />\n' +
+    '    <access origin="*" />\n' +
+    '</widget>\n';
+
 function uninstallPromise(f) {
     return f.then(function() { done = true; }, function(err) { done = err; });
 }
 
 describe('start', function() {
+    beforeEach(function () {
+        var origParseElementtreeSync = xmlHelpers.parseElementtreeSync.bind(xmlHelpers);
+        spyOn(xmlHelpers, 'parseElementtreeSync').andCallFake(function(path) {
+            if (/config.xml$/.test(path)) return new et.ElementTree(et.XML(TEST_XML));
+            return origParseElementtreeSync(path);
+        });
+    });
 
     it('start', function() {
         shell.rm('-rf', project, project2, project3);
@@ -89,48 +114,29 @@ describe('start', function() {
 });
 
 describe('uninstallPlatform', function() {
-    var proc, prepare, actions_push, add_to_queue, c_a, rm;
+    var proc, rm;
     var fsWrite;
 
     beforeEach(function() {
         proc = spyOn(actions.prototype, 'process').andReturn(Q());
-        actions_push = spyOn(actions.prototype, 'push');
-        c_a = spyOn(actions.prototype, 'createAction');
-        prepare = spyOn(plugman, 'prepare');
         fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
         rm = spyOn(shell, 'rm').andReturn(true);
         spyOn(shell, 'cp').andReturn(true);
-        add_to_queue = spyOn(PlatformJson.prototype, 'addUninstalledPluginToPrepareQueue');
         done = false;
     });
     describe('success', function() {
-        it('should call prepare after a successful uninstall', function() {
-            runs(function() {
-                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(prepare).toHaveBeenCalled();
-            });
-        });
-        it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method after processing an install', function() {
-            runs(function() {
-                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(add_to_queue).toHaveBeenCalledWith(dummy_id, true);
-            });
-        });
-        it('should queue up actions as appropriate for that plugin and call process on the action stack', function() {
-            runs(function() {
-                uninstallPromise(uninstall.uninstallPlatform('android', project, dummy_id));
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(actions_push.calls.length).toEqual(6);
-                expect(proc).toHaveBeenCalled();
-            });
+
+        it('should get PlatformApi instance for platform and invoke its\' removePlugin method', function(done) {
+            var platformApi = { removePlugin: jasmine.createSpy('removePlugin').andReturn(Q()) };
+            var getPlatformApi = spyOn(platforms, 'getPlatformApi').andReturn(platformApi);
+
+            uninstall.uninstallPlatform('android', project, dummy_id)
+            .then(function() {
+                expect(getPlatformApi).toHaveBeenCalledWith('android', project);
+                expect(platformApi.removePlugin).toHaveBeenCalled();
+            }, function(err) {
+                expect(err).toBeUndefined();
+            }).fin(done);
         });
 
         describe('with dependencies', function() {
@@ -255,25 +261,13 @@ describe('uninstallPlugin', function() {
 });
 
 describe('uninstall', function() {
-    var fsWrite, rm, add_to_queue;
+    var fsWrite, rm;
 
     beforeEach(function() {
         fsWrite = spyOn(fs, 'writeFileSync').andReturn(true);
         rm = spyOn(shell, 'rm').andReturn(true);
-        add_to_queue = spyOn(PlatformJson.prototype, 'addUninstalledPluginToPrepareQueue');
         done = false;
     });
-    describe('success', function() {
-        it('should call the config-changes module\'s add_uninstalled_plugin_to_prepare_queue method after processing an install', function() {
-            runs(function() {
-                uninstallPromise( uninstall('android', project, plugins['org.test.plugins.dummyplugin']) );
-            });
-            waitsFor(function() { return done; }, 'promise never resolved', 200);
-            runs(function() {
-                expect(add_to_queue).toHaveBeenCalledWith(dummy_id, true);
-            });
-        });
-    });
 
     describe('failure', function() {
         it('should throw if platform is unrecognized', function() {
@@ -298,29 +292,20 @@ describe('uninstall', function() {
 });
 
 describe('end', function() {
-
     it('end', function() {
         done = false;
 
-        promise.then(
-            function(){
-                return uninstall('android', project, plugins['org.test.plugins.dummyplugin']);
-            }
-        ).then(
-            function(){
-                // Fails... A depends on
-                return uninstall('android', project, plugins['C']);
-            }
-        ).fail(
-            function(err) {
-                expect(err.stack).toMatch(/The plugin 'C' is required by \(A\), skipping uninstallation./);
-            }
-        ).then(
-            function(){
-                // dependencies on C,D ... should this only work with --recursive? prompt user..?
-                return uninstall('android', project, plugins['A']);
-            }
-        ).fin(function(err){
+        promise.then(function(){
+            return uninstall('android', project, plugins['org.test.plugins.dummyplugin']);
+        }).then(function(){
+            // Fails... A depends on
+            return uninstall('android', project, plugins['C']);
+        }).fail(function(err) {
+            expect(err.stack).toMatch(/The plugin 'C' is required by \(A\), skipping uninstallation./);
+        }).then(function(){
+            // dependencies on C,D ... should this only work with --recursive? prompt user..?
+            return uninstall('android', project, plugins['A']);
+        }).fin(function(err){
             if(err)
                 plugman.emit('error', err);
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07271a5c/cordova-lib/spec-plugman/util/action-stack.spec.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/util/action-stack.spec.js b/cordova-lib/spec-plugman/util/action-stack.spec.js
index 5eb4847..c0677ea 100644
--- a/cordova-lib/spec-plugman/util/action-stack.spec.js
+++ b/cordova-lib/spec-plugman/util/action-stack.spec.js
@@ -37,9 +37,9 @@ describe('action-stack', function() {
             stack.push(stack.createAction(second_spy, second_args, function(){}, []));
             stack.push(stack.createAction(third_spy, third_args, function(){}, []));
             stack.process('android', android_one_project);
-            expect(first_spy).toHaveBeenCalledWith(first_args[0], jasmine.any(Object));
-            expect(second_spy).toHaveBeenCalledWith(second_args[0], jasmine.any(Object));
-            expect(third_spy).toHaveBeenCalledWith(third_args[0], jasmine.any(Object));
+            expect(first_spy).toHaveBeenCalledWith(first_args[0]);
+            expect(second_spy).toHaveBeenCalledWith(second_args[0]);
+            expect(third_spy).toHaveBeenCalledWith(third_args[0]);
         });
         it('should revert processed actions if an exception occurs', function() {
             spyOn(console, 'log');
@@ -66,11 +66,11 @@ describe('action-stack', function() {
             runs(function() {
                 expect(error).toEqual(process_err);
                 // first two actions should have been called, but not the third
-                expect(first_spy).toHaveBeenCalledWith(first_args[0], jasmine.any(Object));
-                expect(second_spy).toHaveBeenCalledWith(second_args[0], jasmine.any(Object));
-                expect(third_spy).not.toHaveBeenCalledWith(third_args[0], jasmine.any(Object));
+                expect(first_spy).toHaveBeenCalledWith(first_args[0]);
+                expect(second_spy).toHaveBeenCalledWith(second_args[0]);
+                expect(third_spy).not.toHaveBeenCalledWith(third_args[0]);
                 // first reverter should have been called after second action exploded
-                expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0], jasmine.any(Object));
+                expect(first_reverter).toHaveBeenCalledWith(first_reverter_args[0]);
             });
         });
     });


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org