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:40 UTC

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

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