cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ste...@apache.org
Subject [15/16] cordova-lib git commit: CB-11346: added tests for support of 3rd party platforms
Date Tue, 21 Mar 2017 20:19:31 GMT
http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/Api.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/Api.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/Api.js
new file mode 100644
index 0000000..8e4711c
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/Api.js
@@ -0,0 +1,415 @@
+/**
+    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 Q = require('q');
+
+var AndroidProject = require('./lib/AndroidProject');
+var AndroidStudio = require('./lib/AndroidStudio');
+var PluginManager = require('cordova-common').PluginManager;
+
+var CordovaLogger = require('cordova-common').CordovaLogger;
+var selfEvents = require('cordova-common').events;
+
+var PLATFORM = 'android';
+
+
+function setupEvents(externalEventEmitter) {
+    if (externalEventEmitter) {
+        // This will make the platform internal events visible outside
+        selfEvents.forwardEventsTo(externalEventEmitter);
+        return externalEventEmitter;
+    }
+
+    // There is no logger if external emitter is not present,
+    // so attach a console logger
+    CordovaLogger.get().subscribe(selfEvents);
+    return selfEvents;
+}
+
+
+/**
+ * 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 Api(platform, platformRootDir, events) {
+    this.platform = PLATFORM;
+    this.root = path.resolve(__dirname, '..');
+
+    setupEvents(events);
+
+    var self = this;
+
+    this.locations = {
+        root: self.root,
+        www: path.join(self.root, 'assets/www'),
+        res: path.join(self.root, 'res'),
+        platformWww: path.join(self.root, 'platform_www'),
+        configXml: path.join(self.root, 'res/xml/config.xml'),
+        defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'),
+        strings: path.join(self.root, 'res/values/strings.xml'),
+        manifest: path.join(self.root, 'AndroidManifest.xml'),
+        build: path.join(self.root, 'build'),
+        // NOTE: Due to platformApi spec we need to return relative paths here
+        cordovaJs: 'bin/templates/project/assets/www/cordova.js',
+        cordovaJsSrc: 'cordova-js-src'
+    };
+
+    // XXX Override some locations for Android Studio projects
+    if(AndroidStudio.isAndroidStudioProject(self.root) === true) {
+      selfEvents.emit('log', 'Android Studio project detected');
+      this.android_studio = true;
+      this.locations.configXml = path.join(self.root, 'app/src/main/res/xml/config.xml');
+      this.locations.strings = path.join(self.root, 'app/src/main/res/xml/strings.xml');
+      this.locations.manifest = path.join(self.root, 'app/src/main/AndroidManifest.xml');
+      this.locations.www = path.join(self.root, 'app/src/main/assets/www');
+      this.locations.res = path.join(self.root, 'app/src/main/res');
+    }
+}
+
+/**
+ * Installs platform to specified directory and creates a platform project.
+ *
+ * @param  {String}  destination Destination directory, where insatll platform to
+ * @param  {ConfigParser}  [config] ConfgiParser instance, used to retrieve
+ *   project creation options, such as package id and project name.
+ * @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.
+ * @param {EventEmitter} [events] An EventEmitter instance that will be used for
+ *   logging purposes. If no EventEmitter provided, all events will be logged to
+ *   console
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ *   instance or rejected with CordovaError.
+ */
+Api.createPlatform = function (destination, config, options, events) {
+    events = setupEvents(events);
+    var result;
+    try {
+        result = require('../../lib/create')
+        .create(destination, config, options, events)
+        .then(function (destination) {
+            var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
+            return new PlatformApi(PLATFORM, destination, events);
+        });
+    }
+    catch (e) {
+        events.emit('error','createPlatform is not callable from the android project API.');
+        throw(e);
+    }
+    return result;
+};
+
+/**
+ * Updates already installed platform.
+ *
+ * @param  {String}  destination Destination directory, where platform installed
+ * @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.
+ * @param {EventEmitter} [events] An EventEmitter instance that will be used for
+ *   logging purposes. If no EventEmitter provided, all events will be logged to
+ *   console
+ *
+ * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
+ *   instance or rejected with CordovaError.
+ */
+Api.updatePlatform = function (destination, options, events) {
+    events = setupEvents(events);
+    var result;
+    try {
+        result = require('../../lib/create')
+        .update(destination, options, events)
+        .then(function (destination) {
+            var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
+            return new PlatformApi('android', destination, events);
+        });
+    }
+    catch (e) {
+        events.emit('error','updatePlatform is not callable from the android project API, you will need to do this manually.');
+        throw(e);
+    }
+    return result;
+};
+
+/**
+ * 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.
+ */
+Api.prototype.getPlatformInfo = function () {
+    var result = {};
+    result.locations = this.locations;
+    result.root = this.root;
+    result.name = this.platform;
+    result.version = require('./version');
+    result.projectConfig = this._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.
+ */
+Api.prototype.prepare = function (cordovaProject, prepareOptions) {
+    return require('./lib/prepare').prepare.call(this, cordovaProject, prepareOptions);
+};
+
+/**
+ * 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.
+ */
+Api.prototype.addPlugin = function (plugin, installOptions) {
+    var project = AndroidProject.getProjectFile(this.root);
+    var self = this;
+
+    installOptions = installOptions || {};
+    installOptions.variables = installOptions.variables || {};
+    // Add PACKAGE_NAME variable into vars
+    if (!installOptions.variables.PACKAGE_NAME) {
+        installOptions.variables.PACKAGE_NAME = project.getPackageName();
+    }
+
+    if(this.android_studio === true) {
+      installOptions.android_studio = true;
+    }
+
+    return Q()
+       .then(function () {
+            //CB-11964: Do a clean when installing the plugin code to get around
+            //the Gradle bug introduced by the Android Gradle Plugin Version 2.2
+            //TODO: Delete when the next version of Android Gradle plugin comes out
+
+           // Since clean doesn't just clean the build, it also wipes out www, we need
+           // to pass additional options.
+
+           // Do some basic argument parsing
+            var opts = {};
+
+             // Skip cleaning prepared files when not invoking via cordova CLI.
+            opts.noPrepare = true;
+
+            if(!AndroidStudio.isAndroidStudioProject(self.root) && !project.isClean()) {
+              return self.clean(opts);
+            }
+        })
+       .then(function () {
+            return PluginManager.get(self.platform, self.locations, project)
+                .addPlugin(plugin, installOptions);
+        })
+      .then(function () {
+            if (plugin.getFrameworks(this.platform).length === 0) return;
+
+            selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
+            require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
+        }.bind(this))
+       // CB-11022 Return truthy value to prevent running prepare after
+        .thenResolve(true);
+};
+
+/**
+ * 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.
+ */
+Api.prototype.removePlugin = function (plugin, uninstallOptions) {
+    var project = AndroidProject.getProjectFile(this.root);
+
+    if(uninstallOptions && uninstallOptions.usePlatformWww === true && this.android_studio === true) {
+      uninstallOptions.usePlatformWww = false;
+      uninstallOptions.android_studio = true;
+    }
+
+    return PluginManager.get(this.platform, this.locations, project)
+        .removePlugin(plugin, uninstallOptions)
+        .then(function () {
+            if (plugin.getFrameworks(this.platform).length === 0) return;
+
+            selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
+            require('./lib/builders/builders').getBuilder('gradle').prepBuildFiles();
+        }.bind(this))
+        // CB-11022 Return truthy value to prevent running prepare after
+        .thenResolve(true);
+};
+
+/**
+ * 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.
+ */
+Api.prototype.build = function (buildOptions) {
+    var self = this;
+    return require('./lib/check_reqs').run()
+    .then(function () {
+        return require('./lib/build').run.call(self, buildOptions);
+    })
+    .then(function (buildResults) {
+        // Cast build result to array of build artifacts
+        return buildResults.apkPaths.map(function (apkPath) {
+            return {
+                buildType: buildResults.buildType,
+                buildMethod: buildResults.buildMethod,
+                path: apkPath,
+                type: 'apk'
+            };
+        });
+    });
+};
+
+/**
+ * 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.
+ */
+Api.prototype.run = function(runOptions) {
+    var self = this;
+    return require('./lib/check_reqs').run()
+    .then(function () {
+        return require('./lib/run').run.call(self, runOptions);
+    });
+};
+
+/**
+ * Cleans out the build artifacts from platform's directory, and also
+ * cleans out the platform www directory if called without options specified.
+ *
+ * @return  {Promise}  Return a promise either fulfilled, or rejected with
+ *   CordovaError.
+ */
+Api.prototype.clean = function(cleanOptions) {
+    var self = this;
+    return require('./lib/check_reqs').run()
+      .then(function () {
+          return require('./lib/build').runClean.call(self, cleanOptions);
+      })
+      .then(function () {
+          return require('./lib/prepare').clean.call(self, cleanOptions);
+      });
+};
+
+
+
+/**
+ * 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.
+ */
+Api.prototype.requirements = function() {
+    return require('./lib/check_reqs').check_all();
+};
+
+module.exports = Api;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build
new file mode 100755
index 0000000..222e84a
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+
+/*
+       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 args  = process.argv;
+var Api = require('./Api');
+var nopt = require('nopt');
+var path = require('path');
+
+// Support basic help commands
+if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0)
+    require('./lib/build').help();
+
+// Do some basic argument parsing
+var buildOpts = nopt({
+    'verbose' : Boolean,
+    'silent' : Boolean,
+    'debug' : Boolean,
+    'release' : Boolean,
+    'nobuild': Boolean,
+    'buildConfig' : path
+}, { 'd' : '--verbose' });
+
+// Make buildOptions compatible with PlatformApi build method spec
+buildOpts.argv = buildOpts.argv.original;
+
+require('./loggingHelper').adjustLoggerLevel(buildOpts);
+
+new Api().build(buildOpts)
+.catch(function(err) {
+    console.error(err.stack);
+    process.exit(2);
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build.bat
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build.bat b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build.bat
new file mode 100644
index 0000000..46e966a
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/build.bat
@@ -0,0 +1,26 @@
+:: 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.
+
+@ECHO OFF
+SET script_path="%~dp0build"
+IF EXIST %script_path% (
+        node %script_path% %*
+) ELSE (
+    ECHO.
+    ECHO ERROR: Could not find 'build' script in 'cordova' folder, aborting...>&2
+    EXIT /B 1
+)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean
new file mode 100755
index 0000000..22065cc
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+
+/*
+       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 Api = require('./Api');
+var path  = require('path');
+var nopt = require('nopt');
+
+// Support basic help commands
+if(['--help', '/?', '-h', 'help', '-help', '/help'].indexOf(process.argv[2]) >= 0) {
+    console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]));
+    console.log('Cleans the project directory.');
+    process.exit(0);
+}
+
+// Do some basic argument parsing
+var opts = nopt({
+    'verbose' : Boolean,
+    'silent' : Boolean
+}, { 'd' : '--verbose' });
+
+// Make buildOptions compatible with PlatformApi clean method spec
+opts.argv = opts.argv.original;
+
+// Skip cleaning prepared files when not invoking via cordova CLI.
+opts.noPrepare = true;
+
+require('./loggingHelper').adjustLoggerLevel(opts);
+
+new Api().clean(opts)
+.catch(function(err) {
+    console.error(err.stack);
+    process.exit(2);
+});

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean.bat
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean.bat b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean.bat
new file mode 100644
index 0000000..445ef6e
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/clean.bat
@@ -0,0 +1,26 @@
+:: 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.
+
+@ECHO OFF
+SET script_path="%~dp0clean"
+IF EXIST %script_path% (
+        node %script_path% %*
+) ELSE (
+    ECHO.
+    ECHO ERROR: Could not find 'clean' script in 'cordova' folder, aborting...>&2
+    EXIT /B 1
+)
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/defaults.xml
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/defaults.xml b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/defaults.xml
new file mode 100644
index 0000000..5286ab9
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/defaults.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<widget xmlns     = "http://www.w3.org/ns/widgets"
+        id        = "io.cordova.helloCordova"
+        version   = "2.0.0">
+
+    <!-- Preferences for Android -->
+    <preference name="loglevel" value="DEBUG" />
+</widget>

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/Adb.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/Adb.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/Adb.js
new file mode 100644
index 0000000..84ae707
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/Adb.js
@@ -0,0 +1,105 @@
+/**
+    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 os = require('os');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+
+var Adb = {};
+
+function isDevice(line) {
+    return line.match(/\w+\tdevice/) && !line.match(/emulator/);
+}
+
+function isEmulator(line) {
+    return line.match(/device/) && line.match(/emulator/);
+}
+
+/**
+ * Lists available/connected devices and emulators
+ *
+ * @param   {Object}   opts            Various options
+ * @param   {Boolean}  opts.emulators  Specifies whether this method returns
+ *   emulators only
+ *
+ * @return  {Promise<String[]>}        list of available/connected
+ *   devices/emulators
+ */
+Adb.devices = function (opts) {
+    return spawn('adb', ['devices'], {cwd: os.tmpdir()})
+    .then(function(output) {
+        return output.split('\n').filter(function (line) {
+            // Filter out either real devices or emulators, depending on options
+            return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
+        }).map(function (line) {
+            return line.replace(/\tdevice/, '').replace('\r', '');
+        });
+    });
+};
+
+Adb.install = function (target, packagePath, opts) {
+    events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
+    var args = ['-s', target, 'install'];
+    if (opts && opts.replace) args.push('-r');
+    return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()})
+    .then(function(output) {
+        // 'adb install' seems to always returns no error, even if installation fails
+        // so we catching output to detect installation failure
+        if (output.match(/Failure/)) {
+            if (output.match(/INSTALL_PARSE_FAILED_NO_CERTIFICATES/)) {
+                output += '\n\n' + 'Sign the build using \'-- --keystore\' or \'--buildConfig\'' +
+                    ' or sign and deploy the unsigned apk manually using Android tools.';
+            } else if (output.match(/INSTALL_FAILED_VERSION_DOWNGRADE/)) {
+                output += '\n\n' + 'You\'re trying to install apk with a lower versionCode that is already installed.' +
+                    '\nEither uninstall an app or increment the versionCode.';
+            }
+
+            return Q.reject(new CordovaError('Failed to install apk to device: ' + output));
+        }
+    });
+};
+
+Adb.uninstall = function (target, packageId) {
+    events.emit('verbose', 'Uninstalling package ' + packageId + ' from target ' + target + '...');
+    return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()});
+};
+
+Adb.shell = function (target, shellCommand) {
+    events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...');
+    var args = ['-s', target, 'shell'];
+    shellCommand = shellCommand.split(/\s+/);
+    return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()})
+    .catch(function (output) {
+        return Q.reject(new CordovaError('Failed to execute shell command "' +
+            shellCommand + '"" on device: ' + output));
+    });
+};
+
+Adb.start = function (target, activityName) {
+    events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...');
+    return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName)
+    .catch(function (output) {
+        return Q.reject(new CordovaError('Failed to start application "' +
+            activityName + '"" on device: ' + output));
+    });
+};
+
+module.exports = Adb;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidManifest.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidManifest.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidManifest.js
new file mode 100644
index 0000000..8248f59
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidManifest.js
@@ -0,0 +1,161 @@
+/**
+    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 fs = require('fs');
+var et = require('elementtree');
+var xml= require('cordova-common').xmlHelpers;
+
+var DEFAULT_ORIENTATION = 'default';
+
+/** Wraps an AndroidManifest file */
+function AndroidManifest(path) {
+    this.path = path;
+    this.doc = xml.parseElementtreeSync(path);
+    if (this.doc.getroot().tag !== 'manifest') {
+        throw new Error('AndroidManifest at ' + path + ' has incorrect root node name (expected "manifest")');
+    }
+}
+
+AndroidManifest.prototype.getVersionName = function() {
+    return this.doc.getroot().attrib['android:versionName'];
+};
+
+AndroidManifest.prototype.setVersionName = function(versionName) {
+    this.doc.getroot().attrib['android:versionName'] = versionName;
+    return this;
+};
+
+AndroidManifest.prototype.getVersionCode = function() {
+    return this.doc.getroot().attrib['android:versionCode'];
+};
+
+AndroidManifest.prototype.setVersionCode = function(versionCode) {
+    this.doc.getroot().attrib['android:versionCode'] = versionCode;
+    return this;
+};
+
+AndroidManifest.prototype.getPackageId = function() {
+    /*jshint -W069 */
+    return this.doc.getroot().attrib['package'];
+    /*jshint +W069 */
+};
+
+AndroidManifest.prototype.setPackageId = function(pkgId) {
+    /*jshint -W069 */
+    this.doc.getroot().attrib['package'] = pkgId;
+    /*jshint +W069 */
+    return this;
+};
+
+AndroidManifest.prototype.getActivity = function() {
+    var activity = this.doc.getroot().find('./application/activity');
+    return {
+        getName: function () {
+            return activity.attrib['android:name'];
+        },
+        setName: function (name) {
+            if (!name) {
+                delete activity.attrib['android:name'];
+            } else {
+                activity.attrib['android:name'] = name;
+            }
+            return this;
+        },
+        getOrientation: function () {
+            return activity.attrib['android:screenOrientation'];
+        },
+        setOrientation: function (orientation) {
+            if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) {
+                delete activity.attrib['android:screenOrientation'];
+            } else {
+                activity.attrib['android:screenOrientation'] = orientation;
+            }
+            return this;
+        },
+        getLaunchMode: function () {
+            return activity.attrib['android:launchMode'];
+        },
+        setLaunchMode: function (launchMode) {
+            if (!launchMode) {
+                delete activity.attrib['android:launchMode'];
+            } else {
+                activity.attrib['android:launchMode'] = launchMode;
+            }
+            return this;
+        }
+    };
+};
+
+['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion']
+.forEach(function(sdkPrefName) {
+    // Copy variable reference to avoid closure issues
+    var prefName = sdkPrefName;
+
+    AndroidManifest.prototype['get' + capitalize(prefName)] = function() {
+        var usesSdk = this.doc.getroot().find('./uses-sdk');
+        return usesSdk && usesSdk.attrib['android:' + prefName];
+    };
+
+    AndroidManifest.prototype['set' + capitalize(prefName)] = function(prefValue) {
+        var usesSdk = this.doc.getroot().find('./uses-sdk');
+
+        if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first
+            usesSdk = new et.Element('uses-sdk');
+            this.doc.getroot().append(usesSdk);
+        }
+
+        if (prefValue) {
+            usesSdk.attrib['android:' + prefName] = prefValue;
+        }
+
+        return this;
+    };
+});
+
+AndroidManifest.prototype.getDebuggable = function() {
+    return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true';
+};
+
+AndroidManifest.prototype.setDebuggable = function(value) {
+    var application = this.doc.getroot().find('./application');
+    if (value) {
+        application.attrib['android:debuggable'] = 'true';
+    } else {
+        // The default value is "false", so we can remove attribute at all.
+        delete application.attrib['android:debuggable'];
+    }
+    return this;
+};
+
+/**
+ * Writes manifest to disk syncronously. If filename is specified, then manifest
+ *   will be written to that file
+ *
+ * @param   {String}  [destPath]  File to write manifest to. If omitted,
+ *   manifest will be written to file it has been read from.
+ */
+AndroidManifest.prototype.write = function(destPath) {
+    fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
+};
+
+module.exports = AndroidManifest;
+
+function capitalize (str) {
+    return str.charAt(0).toUpperCase() + str.slice(1);
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidProject.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidProject.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidProject.js
new file mode 100644
index 0000000..fa1c612
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidProject.js
@@ -0,0 +1,210 @@
+/**
+    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 fs = require('fs');
+var path = require('path');
+var properties_parser = require('properties-parser');
+var AndroidManifest = require('./AndroidManifest');
+var AndroidStudio = require('./AndroidStudio');
+var pluginHandlers = require('./pluginHandlers');
+
+var projectFileCache = {};
+
+function addToPropertyList(projectProperties, key, value) {
+    var i = 1;
+    while (projectProperties.get(key + '.' + i))
+        i++;
+
+    projectProperties.set(key + '.' + i, value);
+    projectProperties.dirty = true;
+}
+
+function removeFromPropertyList(projectProperties, key, value) {
+    var i = 1;
+    var currentValue;
+    while ((currentValue = projectProperties.get(key + '.' + i))) {
+        if (currentValue === value) {
+            while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) {
+                projectProperties.set(key + '.' + i, currentValue);
+                i++;
+            }
+            projectProperties.set(key + '.' + i);
+            break;
+        }
+        i++;
+    }
+    projectProperties.dirty = true;
+}
+
+function getRelativeLibraryPath (parentDir, subDir) {
+    var libraryPath = path.relative(parentDir, subDir);
+    return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath;
+}
+
+function AndroidProject(projectDir) {
+    this._propertiesEditors = {};
+    this._subProjectDirs = {};
+    this._dirty = false;
+    this.projectDir = projectDir;
+    this.platformWww = path.join(this.projectDir, 'platform_www');
+    this.www = path.join(this.projectDir, 'assets/www');
+    if(AndroidStudio.isAndroidStudioProject(projectDir) === true) {
+      this.www = path.join(this.projectDir, 'app/src/main/assets/www');
+    }
+}
+
+AndroidProject.getProjectFile = function (projectDir) {
+    if (!projectFileCache[projectDir]) {
+        projectFileCache[projectDir] = new AndroidProject(projectDir);
+    }
+
+    return projectFileCache[projectDir];
+};
+
+AndroidProject.purgeCache = function (projectDir) {
+    if (projectDir) {
+        delete projectFileCache[projectDir];
+    } else {
+        projectFileCache = {};
+    }
+};
+
+/**
+ * Reads the package name out of the Android Manifest file
+ *
+ * @param   {String}  projectDir  The absolute path to the directory containing the project
+ *
+ * @return  {String}              The name of the package
+ */
+AndroidProject.prototype.getPackageName = function() {
+    var manifestPath = path.join(this.projectDir, 'AndroidManifest.xml');
+    if(AndroidStudio.isAndroidStudioProject(this.projectDir) === true) {
+      manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml');
+    }
+    return new AndroidManifest(manifestPath).getPackageId();
+};
+
+AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, src) {
+    // All custom subprojects are prefixed with the last portion of the package id.
+    // This is to avoid collisions when opening multiple projects in Eclipse that have subprojects with the same name.
+    var packageName = this.getPackageName();
+    var lastDotIndex = packageName.lastIndexOf('.');
+    var prefix = packageName.substring(lastDotIndex + 1);
+    var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src));
+    return subRelativeDir;
+};
+
+AndroidProject.prototype.addSubProject = function(parentDir, subDir) {
+    var parentProjectFile = path.resolve(parentDir, 'project.properties');
+    var subProjectFile = path.resolve(subDir, 'project.properties');
+    var parentProperties = this._getPropertiesFile(parentProjectFile);
+    // TODO: Setting the target needs to happen only for pre-3.7.0 projects
+    if (fs.existsSync(subProjectFile)) {
+        var subProperties = this._getPropertiesFile(subProjectFile);
+        subProperties.set('target', parentProperties.get('target'));
+        subProperties.dirty = true;
+        this._subProjectDirs[subDir] = true;
+    }
+    addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
+
+    this._dirty = true;
+};
+
+AndroidProject.prototype.removeSubProject = function(parentDir, subDir) {
+    var parentProjectFile = path.resolve(parentDir, 'project.properties');
+    var parentProperties = this._getPropertiesFile(parentProjectFile);
+    removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
+    delete this._subProjectDirs[subDir];
+    this._dirty = true;
+};
+
+AndroidProject.prototype.addGradleReference = function(parentDir, subDir) {
+    var parentProjectFile = path.resolve(parentDir, 'project.properties');
+    var parentProperties = this._getPropertiesFile(parentProjectFile);
+    addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
+    this._dirty = true;
+};
+
+AndroidProject.prototype.removeGradleReference = function(parentDir, subDir) {
+    var parentProjectFile = path.resolve(parentDir, 'project.properties');
+    var parentProperties = this._getPropertiesFile(parentProjectFile);
+    removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
+    this._dirty = true;
+};
+
+AndroidProject.prototype.addSystemLibrary = function(parentDir, value) {
+    var parentProjectFile = path.resolve(parentDir, 'project.properties');
+    var parentProperties = this._getPropertiesFile(parentProjectFile);
+    addToPropertyList(parentProperties, 'cordova.system.library', value);
+    this._dirty = true;
+};
+
+AndroidProject.prototype.removeSystemLibrary = function(parentDir, value) {
+    var parentProjectFile = path.resolve(parentDir, 'project.properties');
+    var parentProperties = this._getPropertiesFile(parentProjectFile);
+    removeFromPropertyList(parentProperties, 'cordova.system.library', value);
+    this._dirty = true;
+};
+
+AndroidProject.prototype.write = function() {
+    if (!this._dirty) {
+        return;
+    }
+    this._dirty = false;
+
+    for (var filename in this._propertiesEditors) {
+        var editor = this._propertiesEditors[filename];
+        if (editor.dirty) {
+            fs.writeFileSync(filename, editor.toString());
+            editor.dirty = false;
+        }
+    }
+};
+
+AndroidProject.prototype._getPropertiesFile = function (filename) {
+    if (!this._propertiesEditors[filename]) {
+        if (fs.existsSync(filename)) {
+            this._propertiesEditors[filename] = properties_parser.createEditor(filename);
+        } else {
+            this._propertiesEditors[filename] = properties_parser.createEditor();
+        }
+    }
+
+    return this._propertiesEditors[filename];
+};
+
+AndroidProject.prototype.getInstaller = function (type) {
+    return pluginHandlers.getInstaller(type);
+};
+
+AndroidProject.prototype.getUninstaller = function (type) {
+    return pluginHandlers.getUninstaller(type);
+};
+
+/*
+ * This checks if an Android project is clean or has old build artifacts
+ */
+
+AndroidProject.prototype.isClean = function() {
+    var build_path = path.join(this.projectDir, 'build');
+    //If the build directory doesn't exist, it's clean
+    return !(fs.existsSync(build_path));
+};
+
+module.exports = AndroidProject;

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidStudio.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidStudio.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidStudio.js
new file mode 100644
index 0000000..335b334
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/AndroidStudio.js
@@ -0,0 +1,42 @@
+/*
+ *  This is a simple routine that checks if project is an Android Studio Project
+ *
+ *  @param {String} root Root folder of the project
+ */
+
+/*jshint esnext: false */
+
+var path = require('path');
+var fs = require('fs');
+var CordovaError = require('cordova-common').CordovaError;
+
+module.exports.isAndroidStudioProject = function isAndroidStudioProject(root) {
+    var eclipseFiles = ['AndroidManifest.xml', 'libs', 'res', 'project.properties', 'platform_www'];
+    var androidStudioFiles = ['app', 'gradle', 'app/src/main/res'];
+
+    // assume it is an AS project and not an Eclipse project
+    var isEclipse = false;
+    var isAS = true;
+
+    if(!fs.existsSync(root)) {
+        throw new CordovaError('AndroidStudio.js:inAndroidStudioProject root does not exist: ' + root);
+    }
+
+    // if any of the following exists, then we are not an ASProj
+    eclipseFiles.forEach(function(file) {
+        if(fs.existsSync(path.join(root, file))) {
+            isEclipse = true;
+        }
+    });
+
+    // if it is NOT an eclipse project, check that all required files exist
+    if(!isEclipse) {
+        androidStudioFiles.forEach(function(file){
+            if(!fs.existsSync(path.join(root, file))) {
+                console.log('missing file :: ' + file);
+                isAS = false;
+            }
+        });
+    }
+    return (!isEclipse && isAS);
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/build.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/build.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/build.js
new file mode 100644
index 0000000..bd613da
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/build.js
@@ -0,0 +1,301 @@
+#!/usr/bin/env node
+
+/*
+       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'),
+    path    = require('path'),
+    fs      = require('fs'),
+    nopt = require('nopt');
+
+var Adb = require('./Adb');
+
+var builders = require('./builders/builders');
+var events = require('cordova-common').events;
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+
+function parseOpts(options, resolvedTarget, projectRoot) {
+    options = options || {};
+    options.argv = nopt({
+        gradle: Boolean,
+        ant: Boolean,
+        prepenv: Boolean,
+        versionCode: String,
+        minSdkVersion: String,
+        gradleArg: [String, Array],
+        keystore: path,
+        alias: String,
+        storePassword: String,
+        password: String,
+        keystoreType: String
+    }, {}, options.argv, 0);
+
+    var ret = {
+        buildType: options.release ? 'release' : 'debug',
+        buildMethod: process.env.ANDROID_BUILD || 'gradle',
+        prepEnv: options.argv.prepenv,
+        arch: resolvedTarget && resolvedTarget.arch,
+        extraArgs: []
+    };
+
+    if (options.argv.ant || options.argv.gradle)
+        ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
+
+    if (options.nobuild) ret.buildMethod = 'none';
+
+    if (options.argv.versionCode)
+        ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode);
+
+    if (options.argv.minSdkVersion)
+        ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion);
+
+    if (options.argv.gradleArg) {
+        ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
+    }
+
+    var packageArgs = {};
+
+    if (options.argv.keystore)
+        packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore));
+
+    ['alias','storePassword','password','keystoreType'].forEach(function (flagName) {
+        if (options.argv[flagName])
+            packageArgs[flagName] = options.argv[flagName];
+    });
+
+    var buildConfig = options.buildConfig;
+
+    // If some values are not specified as command line arguments - use build config to supplement them.
+    // Command line arguemnts have precedence over build config.
+    if (buildConfig) {
+        if (!fs.existsSync(buildConfig)) {
+            throw new Error('Specified build config file does not exist: ' + buildConfig);
+        }
+        events.emit('log', 'Reading build config file: '+ path.resolve(buildConfig));
+        var buildjson = fs.readFileSync(buildConfig, 'utf8');
+        var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
+        if (config.android && config.android[ret.buildType]) {
+            var androidInfo = config.android[ret.buildType];
+            if(androidInfo.keystore && !packageArgs.keystore) {
+                if(androidInfo.keystore.substr(0,1) === '~') {
+                    androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
+                }
+                packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
+                events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
+            }
+
+            ['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
+                packageArgs[key] = packageArgs[key] || androidInfo[key];
+            });
+        }
+    }
+
+    if (packageArgs.keystore && packageArgs.alias) {
+        ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
+            packageArgs.password, packageArgs.keystoreType);
+    }
+
+    if(!ret.packageInfo) {
+        if(Object.keys(packageArgs).length > 0) {
+            events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
+        }
+    }
+
+    return ret;
+}
+
+/*
+ * Builds the project with the specifed options
+ * Returns a promise.
+ */
+module.exports.runClean = function(options) {
+    var opts = parseOpts(options, null, this.root);
+    var builder = builders.getBuilder(opts.buildMethod);
+    return builder.prepEnv(opts)
+    .then(function() {
+        return builder.clean(opts);
+    });
+};
+
+/**
+ * Builds the project with the specifed options.
+ *
+ * @param   {BuildOptions}  options      A set of options. See PlatformApi.build
+ *   method documentation for reference.
+ * @param   {Object}  optResolvedTarget  A deployment target. Used to pass
+ *   target architecture from upstream 'run' call. TODO: remove this option in
+ *   favor of setting buildOptions.archs field.
+ *
+ * @return  {Promise<Object>}            Promise, resolved with built packages
+ *   information.
+ */
+module.exports.run = function(options, optResolvedTarget) {
+    var opts = parseOpts(options, optResolvedTarget, this.root);
+    var builder = builders.getBuilder(opts.buildMethod);
+    return builder.prepEnv(opts)
+    .then(function() {
+        if (opts.prepEnv) {
+            events.emit('verbose', 'Build file successfully prepared.');
+            return;
+        }
+        return builder.build(opts)
+        .then(function() {
+            var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
+            events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
+            return {
+                apkPaths: apkPaths,
+                buildType: opts.buildType,
+                buildMethod: opts.buildMethod
+            };
+        });
+    });
+};
+
+/*
+ * Detects the architecture of a device/emulator
+ * Returns "arm" or "x86".
+ */
+module.exports.detectArchitecture = function(target) {
+    function helper() {
+        return Adb.shell(target, 'cat /proc/cpuinfo')
+        .then(function(output) {
+            return /intel/i.exec(output) ? 'x86' : 'arm';
+        });
+    }
+    // It sometimes happens (at least on OS X), that this command will hang forever.
+    // To fix it, either unplug & replug device, or restart adb server.
+    return helper()
+    .timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
+    .then(null, function(err) {
+        if (/timed out/.exec('' + err)) {
+            // adb kill-server doesn't seem to do the trick.
+            // Could probably find a x-platform version of killall, but I'm not actually
+            // sure that this scenario even happens on non-OSX machines.
+            events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
+            return spawn('killall', ['adb'])
+            .then(function() {
+                return helper()
+                .then(null, function() {
+                    // The double kill is sadly often necessary, at least on mac.
+                    events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
+                    return spawn('killall', ['adb'])
+                    .then(function() {
+                        return helper()
+                        .then(null, function() {
+                            return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
+                        });
+                    });
+                });
+            }, function() {
+                // For non-killall OS's.
+                return Q.reject(err);
+            });
+        }
+        throw err;
+    });
+};
+
+module.exports.findBestApkForArchitecture = function(buildResults, arch) {
+    var paths = buildResults.apkPaths.filter(function(p) {
+        var apkName = path.basename(p);
+        if (buildResults.buildType == 'debug') {
+            return /-debug/.exec(apkName);
+        }
+        return !/-debug/.exec(apkName);
+    });
+    var archPattern = new RegExp('-' + arch);
+    var hasArchPattern = /-x86|-arm/;
+    for (var i = 0; i < paths.length; ++i) {
+        var apkName = path.basename(paths[i]);
+        if (hasArchPattern.exec(apkName)) {
+            if (archPattern.exec(apkName)) {
+                return paths[i];
+            }
+        } else {
+            return paths[i];
+        }
+    }
+    throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
+};
+
+function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
+    this.keystore = {
+        'name': 'key.store',
+        'value': keystore
+    };
+    this.alias = {
+        'name': 'key.alias',
+        'value': alias
+    };
+    if (storePassword) {
+        this.storePassword = {
+            'name': 'key.store.password',
+            'value': storePassword
+        };
+    }
+    if (password) {
+        this.password = {
+            'name': 'key.alias.password',
+            'value': password
+        };
+    }
+    if (keystoreType) {
+        this.keystoreType = {
+            'name': 'key.store.type',
+            'value': keystoreType
+        };
+    }
+}
+
+PackageInfo.prototype = {
+    toProperties: function() {
+        var self = this;
+        var result = '';
+        Object.keys(self).forEach(function(key) {
+            result += self[key].name;
+            result += '=';
+            result += self[key].value.replace(/\\/g, '\\\\');
+            result += '\n';
+        });
+        return result;
+    }
+};
+
+module.exports.help = function() {
+    console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
+    console.log('Flags:');
+    console.log('    \'--debug\': will build project in debug mode (default)');
+    console.log('    \'--release\': will build project for release');
+    console.log('    \'--ant\': will build project with ant');
+    console.log('    \'--gradle\': will build project with gradle (default)');
+    console.log('    \'--nobuild\': will skip build process (useful when using run command)');
+    console.log('    \'--prepenv\': don\'t build, but copy in build scripts where necessary');
+    console.log('    \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
+    console.log('    \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
+    console.log('    \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
+    console.log('');
+    console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
+    console.log('    \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
+    console.log('    \'--alias=\': Alias for the key store. (Required)');
+    console.log('    \'--storePassword=\': Password for the key store. (Optional - prompted)');
+    console.log('    \'--password=\': Password for the key. (Optional - prompted)');
+    console.log('    \'--keystoreType\': Type of the keystore. (Optional)');
+    process.exit(0);
+};

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/AntBuilder.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/AntBuilder.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/AntBuilder.js
new file mode 100644
index 0000000..4e0f71a
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/AntBuilder.js
@@ -0,0 +1,156 @@
+/*
+       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 util = require('util');
+var shell = require('shelljs');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var check_reqs = require('../check_reqs');
+
+var SIGNING_PROPERTIES = '-signing.properties';
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var TEMPLATE =
+    '# This file is automatically generated.\n' +
+    '# Do not modify this file -- ' + MARKER + '\n';
+
+var GenericBuilder = require('./GenericBuilder');
+
+function AntBuilder (projectRoot) {
+    GenericBuilder.call(this, projectRoot);
+
+    this.binDirs = {ant: this.binDirs.ant};
+}
+
+util.inherits(AntBuilder, GenericBuilder);
+
+AntBuilder.prototype.getArgs = function(cmd, opts) {
+    var args = [cmd, '-f', path.join(this.root, 'build.xml')];
+    // custom_rules.xml is required for incremental builds.
+    if (hasCustomRules(this.root)) {
+        args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
+    }
+    if(opts.packageInfo) {
+        args.push('-propertyfile=' + path.join(this.root, opts.buildType + SIGNING_PROPERTIES));
+    }
+    return args;
+};
+
+AntBuilder.prototype.prepEnv = function(opts) {
+    var self = this;
+    return check_reqs.check_ant()
+    .then(function() {
+        // Copy in build.xml on each build so that:
+        // A) we don't require the Android SDK at project creation time, and
+        // B) we always use the SDK's latest version of it.
+        /*jshint -W069 */
+        var sdkDir = process.env['ANDROID_HOME'];
+        /*jshint +W069 */
+        var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
+        function writeBuildXml(projectPath) {
+            var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest());
+            fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
+            if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
+                fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
+            }
+        }
+        writeBuildXml(self.root);
+        var propertiesObj = self.readProjectProperties();
+        var subProjects = propertiesObj.libs;
+        for (var i = 0; i < subProjects.length; ++i) {
+            writeBuildXml(path.join(self.root, subProjects[i]));
+        }
+        if (propertiesObj.systemLibs.length > 0) {
+            throw new CordovaError('Project contains at least one plugin that requires a system library. This is not supported with ANT. Use gradle instead.');
+        }
+
+        var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+        var propertiesFilePath = path.join(self.root, propertiesFile);
+        if (opts.packageInfo) {
+            fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+        } else if(isAutoGenerated(propertiesFilePath)) {
+            shell.rm('-f', propertiesFilePath);
+        }
+    });
+};
+
+/*
+ * Builds the project with ant.
+ * Returns a promise.
+ */
+AntBuilder.prototype.build = function(opts) {
+    // Without our custom_rules.xml, we need to clean before building.
+    var ret = Q();
+    if (!hasCustomRules(this.root)) {
+        // clean will call check_ant() for us.
+        ret = this.clean(opts);
+    }
+
+    var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+    return check_reqs.check_ant()
+    .then(function() {
+        return spawn('ant', args, {stdio: 'pipe'});
+    }).progress(function (stdio){
+        if (stdio.stderr) {
+            process.stderr.write(stdio.stderr);
+        } else {
+            process.stdout.write(stdio.stdout);
+        }
+    }).catch(function (error) {
+        if (error.toString().indexOf('Unable to resolve project target') >= 0) {
+            return check_reqs.check_android_target(error).then(function() {
+                // If due to some odd reason - check_android_target succeeds
+                // we should still fail here.
+                return Q.reject(error);
+            });
+        }
+        return Q.reject(error);
+    });
+};
+
+AntBuilder.prototype.clean = function(opts) {
+    var args = this.getArgs('clean', opts);
+    var self = this;
+    return check_reqs.check_ant()
+    .then(function() {
+        return spawn('ant', args, {stdio: 'inherit'});
+    })
+    .then(function () {
+        shell.rm('-rf', path.join(self.root, 'out'));
+
+        ['debug', 'release'].forEach(function(config) {
+            var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES);
+            if(isAutoGenerated(propertiesFilePath)){
+                shell.rm('-f', propertiesFilePath);
+            }
+        });
+    });
+};
+
+module.exports = AntBuilder;
+
+function hasCustomRules(projectRoot) {
+    return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
+}
+
+function isAutoGenerated(file) {
+    return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GenericBuilder.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GenericBuilder.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GenericBuilder.js
new file mode 100644
index 0000000..362da43
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GenericBuilder.js
@@ -0,0 +1,147 @@
+/*
+       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 shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+function GenericBuilder (projectDir) {
+    this.root = projectDir || path.resolve(__dirname, '../../..');
+    this.binDirs = {
+        ant: path.join(this.root, hasCustomRules(this.root) ? 'ant-build' : 'bin'),
+        gradle: path.join(this.root, 'build', 'outputs', 'apk')
+    };
+}
+
+function hasCustomRules(projectRoot) {
+    return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
+}
+
+GenericBuilder.prototype.prepEnv = function() {
+    return Q();
+};
+
+GenericBuilder.prototype.build = function() {
+    events.emit('log', 'Skipping build...');
+    return Q(null);
+};
+
+GenericBuilder.prototype.clean = function() {
+    return Q();
+};
+
+GenericBuilder.prototype.findOutputApks = function(build_type, arch) {
+    var self = this;
+    return Object.keys(this.binDirs)
+    .reduce(function (result, builderName) {
+        var binDir = self.binDirs[builderName];
+        return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch));
+    }, [])
+    .sort(apkSorter);
+};
+
+GenericBuilder.prototype.readProjectProperties = function () {
+    function findAllUniq(data, r) {
+        var s = {};
+        var m;
+        while ((m = r.exec(data))) {
+            s[m[1]] = 1;
+        }
+        return Object.keys(s);
+    }
+
+    var data = fs.readFileSync(path.join(this.root, 'project.properties'), 'utf8');
+    return {
+        libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
+        gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
+        systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
+    };
+};
+
+GenericBuilder.prototype.extractRealProjectNameFromManifest = function () {
+    var manifestPath = path.join(this.root, 'AndroidManifest.xml');
+    var manifestData = fs.readFileSync(manifestPath, 'utf8');
+    var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
+    if (!m) {
+        throw new CordovaError('Could not find package name in ' + manifestPath);
+    }
+
+    var packageName=m[1];
+    var lastDotIndex = packageName.lastIndexOf('.');
+    return packageName.substring(lastDotIndex + 1);
+};
+
+module.exports = GenericBuilder;
+
+function apkSorter(fileA, fileB) {
+    // De-prioritize unsigned builds
+    var unsignedRE = /-unsigned/;
+    if (unsignedRE.exec(fileA)) {
+        return 1;
+    } else if (unsignedRE.exec(fileB)) {
+        return -1;
+    }
+
+    var timeDiff = fs.statSync(fileA).mtime - fs.statSync(fileB).mtime;
+    return timeDiff === 0 ? fileA.length - fileB.length : timeDiff;
+}
+
+function findOutputApksHelper(dir, build_type, arch) {
+    var shellSilent = shell.config.silent;
+    shell.config.silent = true;
+
+    var ret = shell.ls(path.join(dir, '*.apk'))
+    .filter(function(candidate) {
+        var apkName = path.basename(candidate);
+        // Need to choose between release and debug .apk.
+        if (build_type === 'debug') {
+            return /-debug/.exec(apkName) && !/-unaligned|-unsigned/.exec(apkName);
+        }
+        if (build_type === 'release') {
+            return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
+        }
+        return true;
+    })
+    .sort(apkSorter);
+
+    shellSilent = shellSilent;
+
+    if (ret.length === 0) {
+        return ret;
+    }
+    // Assume arch-specific build if newest apk has -x86 or -arm.
+    var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
+    // And show only arch-specific ones (or non-arch-specific)
+    ret = ret.filter(function(p) {
+        /*jshint -W018 */
+        return !!/-x86|-arm/.exec(path.basename(p)) == archSpecific;
+        /*jshint +W018 */
+    });
+
+    if (archSpecific && ret.length > 1 && arch) {
+        ret = ret.filter(function(p) {
+            return path.basename(p).indexOf('-' + arch) != -1;
+        });
+    }
+
+    return ret;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GradleBuilder.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GradleBuilder.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GradleBuilder.js
new file mode 100644
index 0000000..5b5ce13
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/GradleBuilder.js
@@ -0,0 +1,279 @@
+/*
+       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 util = require('util');
+var path = require('path');
+var shell = require('shelljs');
+var spawn = require('cordova-common').superspawn.spawn;
+var CordovaError = require('cordova-common').CordovaError;
+var check_reqs = require('../check_reqs');
+
+var GenericBuilder = require('./GenericBuilder');
+
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var SIGNING_PROPERTIES = '-signing.properties';
+var TEMPLATE =
+    '# This file is automatically generated.\n' +
+    '# Do not modify this file -- ' + MARKER + '\n';
+
+function GradleBuilder (projectRoot) {
+    GenericBuilder.call(this, projectRoot);
+
+    this.binDirs = {gradle: this.binDirs.gradle};
+}
+
+util.inherits(GradleBuilder, GenericBuilder);
+
+GradleBuilder.prototype.getArgs = function(cmd, opts) {
+    if (cmd == 'release') {
+        cmd = 'cdvBuildRelease';
+    } else if (cmd == 'debug') {
+        cmd = 'cdvBuildDebug';
+    }
+    var args = [cmd, '-b', path.join(this.root, 'build.gradle')];
+    if (opts.arch) {
+        args.push('-PcdvBuildArch=' + opts.arch);
+    }
+
+    // 10 seconds -> 6 seconds
+    args.push('-Dorg.gradle.daemon=true');
+    // to allow dex in process
+    args.push('-Dorg.gradle.jvmargs=-Xmx2048m');
+    // allow NDK to be used - required by Gradle 1.5 plugin
+    args.push('-Pandroid.useDeprecatedNdk=true');
+    args.push.apply(args, opts.extraArgs);
+    // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
+    // args.push('-Dorg.gradle.parallel=true');
+    return args;
+};
+
+/*
+ * This returns a promise
+ */
+
+GradleBuilder.prototype.runGradleWrapper = function(gradle_cmd) {
+    var gradlePath = path.join(this.root, 'gradlew');
+    var wrapperGradle = path.join(this.root, 'wrapper.gradle');
+    if(fs.existsSync(gradlePath)) {
+      //Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
+    } else {
+      return spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], {stdio: 'inherit'});
+    }
+};
+
+
+// Makes the project buildable, minus the gradle wrapper.
+GradleBuilder.prototype.prepBuildFiles = function() {
+    // Update the version of build.gradle in each dependent library.
+    var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
+    var propertiesObj = this.readProjectProperties();
+    var subProjects = propertiesObj.libs;
+    var checkAndCopy = function(subProject, root) {
+      var subProjectGradle = path.join(root, subProject, 'build.gradle');
+      // This is the future-proof way of checking if a file exists
+      // This must be synchronous to satisfy a Travis test
+      try {
+          fs.accessSync(subProjectGradle, fs.F_OK);
+      } catch (e) {
+          shell.cp('-f', pluginBuildGradle, subProjectGradle);
+      }
+    };
+    for (var i = 0; i < subProjects.length; ++i) {
+        if (subProjects[i] !== 'CordovaLib') {
+          checkAndCopy(subProjects[i], this.root);
+        }
+    }
+    var name = this.extractRealProjectNameFromManifest();
+    //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
+    var settingsGradlePaths =  subProjects.map(function(p){
+        var realDir=p.replace(/[/\\]/g, ':');
+        var libName=realDir.replace(name+'-','');
+        var str='include ":'+libName+'"\n';
+        if(realDir.indexOf(name+'-')!==-1)
+            str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
+        return str;
+    });
+
+    // Write the settings.gradle file.
+    fs.writeFileSync(path.join(this.root, 'settings.gradle'),
+        '// GENERATED FILE - DO NOT EDIT\n' +
+        'include ":"\n' + settingsGradlePaths.join(''));
+    // Update dependencies within build.gradle.
+    var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
+    var depsList = '';
+    var root = this.root;
+    var insertExclude = function(p) {
+          var gradlePath = path.join(root, p, 'build.gradle');
+          var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
+          if(projectGradleFile.indexOf('CordovaLib') != -1) {
+            depsList += '{\n        exclude module:("CordovaLib")\n    }\n';
+          }
+          else {
+            depsList +='\n';
+          }
+    };
+    subProjects.forEach(function(p) {
+        console.log('Subproject Path: ' + p);
+        var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
+        depsList += '    debugCompile(project(path: "' + libName + '", configuration: "debug"))';
+        insertExclude(p);
+        depsList += '    releaseCompile(project(path: "' + libName + '", configuration: "release"))';
+        insertExclude(p);
+    });
+    // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
+    var SYSTEM_LIBRARY_MAPPINGS = [
+        [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
+        [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
+    ];
+    propertiesObj.systemLibs.forEach(function(p) {
+        var mavenRef;
+        // It's already in gradle form if it has two ':'s
+        if (/:.*:/.exec(p)) {
+            mavenRef = p;
+        } else {
+            for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
+                var pair = SYSTEM_LIBRARY_MAPPINGS[i];
+                if (pair[0].exec(p)) {
+                    mavenRef = p.replace(pair[0], pair[1]);
+                    break;
+                }
+            }
+            if (!mavenRef) {
+                throw new CordovaError('Unsupported system library (does not work with gradle): ' + p);
+            }
+        }
+        depsList += '    compile "' + mavenRef + '"\n';
+    });
+    buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + '    $2');
+    var includeList = '';
+    propertiesObj.gradleIncludes.forEach(function(includePath) {
+        includeList += 'apply from: "' + includePath + '"\n';
+    });
+    buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
+    fs.writeFileSync(path.join(this.root, 'build.gradle'), buildGradle);
+};
+
+GradleBuilder.prototype.prepEnv = function(opts) {
+    var self = this;
+    return check_reqs.check_gradle()
+      .then(function(gradlePath) {
+        return self.runGradleWrapper(gradlePath);
+      }).then(function() {
+          return self.prepBuildFiles();
+      }).then(function() {
+        // We now copy the gradle out of the framework
+        // This is a dirty patch to get the build working
+        /*
+        var wrapperDir = path.join(self.root, 'CordovaLib');
+        if (process.platform == 'win32') {
+            shell.rm('-f', path.join(self.root, 'gradlew.bat'));
+            shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
+        } else {
+            shell.rm('-f', path.join(self.root, 'gradlew'));
+            shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
+        }
+        shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
+        shell.mkdir('-p', path.join(self.root, 'gradle'));
+        shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
+*/
+        // If the gradle distribution URL is set, make sure it points to version we want.
+        // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
+        // For some reason, using ^ and $ don't work.  This does the job, though.
+        var distributionUrlRegex = /distributionUrl.*zip/;
+        /*jshint -W069 */
+        var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-3.3-all.zip';
+        /*jshint +W069 */
+        var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
+        shell.chmod('u+w', gradleWrapperPropertiesPath);
+        shell.sed('-i', distributionUrlRegex, 'distributionUrl='+distributionUrl, gradleWrapperPropertiesPath);
+
+        var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+        var propertiesFilePath = path.join(self.root, propertiesFile);
+        if (opts.packageInfo) {
+            fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+        } else if (isAutoGenerated(propertiesFilePath)) {
+            shell.rm('-f', propertiesFilePath);
+        }
+    });
+};
+
+/*
+ * Builds the project with gradle.
+ * Returns a promise.
+ */
+GradleBuilder.prototype.build = function(opts) {
+    var wrapper = path.join(this.root, 'gradlew');
+    var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+
+    return spawn(wrapper, args, {stdio: 'pipe'})
+    .progress(function (stdio){
+        if (stdio.stderr) {
+            /*
+             * Workaround for the issue with Java printing some unwanted information to
+             * stderr instead of stdout.
+             * This function suppresses 'Picked up _JAVA_OPTIONS' message from being
+             * printed to stderr. See https://issues.apache.org/jira/browse/CB-9971 for
+             * explanation.
+             */
+            var suppressThisLine = /^Picked up _JAVA_OPTIONS: /i.test(stdio.stderr.toString());
+            if (suppressThisLine) {
+                return;
+            }
+            process.stderr.write(stdio.stderr);
+        } else {
+            process.stdout.write(stdio.stdout);
+        }
+    }).catch(function (error) {
+        if (error.toString().indexOf('failed to find target with hash string') >= 0) {
+            return check_reqs.check_android_target(error).then(function() {
+                // If due to some odd reason - check_android_target succeeds
+                // we should still fail here.
+                return Q.reject(error);
+            });
+        }
+        return Q.reject(error);
+    });
+};
+
+GradleBuilder.prototype.clean = function(opts) {
+    var builder = this;
+    var wrapper = path.join(this.root, 'gradlew');
+    var args = builder.getArgs('clean', opts);
+    return Q().then(function() {
+        return spawn(wrapper, args, {stdio: 'inherit'});
+    })
+    .then(function () {
+        shell.rm('-rf', path.join(builder.root, 'out'));
+
+        ['debug', 'release'].forEach(function(config) {
+            var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
+            if(isAutoGenerated(propertiesFilePath)){
+                shell.rm('-f', propertiesFilePath);
+            }
+        });
+    });
+};
+
+module.exports = GradleBuilder;
+
+function isAutoGenerated(file) {
+    return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/da952e07/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/builders.js
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/builders.js b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/builders.js
new file mode 100644
index 0000000..4921c49
--- /dev/null
+++ b/cordova-lib/spec-cordova/fixtures/platforms/atari/bin/templates/cordova/lib/builders/builders.js
@@ -0,0 +1,47 @@
+/*
+       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 CordovaError = require('cordova-common').CordovaError;
+
+var knownBuilders = {
+    ant: 'AntBuilder',
+    gradle: 'GradleBuilder',
+    none: 'GenericBuilder'
+};
+
+/**
+ * Helper method that instantiates and returns a builder for specified build
+ *   type.
+ *
+ * @param   {String}  builderType   Builder name to construct and return. Must
+ *   be one of 'ant', 'gradle' or 'none'
+ *
+ * @return  {Builder}               A builder instance for specified build type.
+ */
+module.exports.getBuilder = function (builderType, projectRoot) {
+    if (!knownBuilders[builderType])
+        throw new CordovaError('Builder ' + builderType + ' is not supported.');
+
+    try {
+        var Builder = require('./' + knownBuilders[builderType]);
+        return new Builder(projectRoot);
+    } catch (err) {
+        throw new CordovaError('Failed to instantiate ' + knownBuilders[builderType] + ' builder: ' + err);
+    }
+};


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


Mime
View raw message