Merge branch 'master' into StudioProjectCompat

This will have to be linted
This commit is contained in:
Joe Bowser 2017-06-26 10:29:31 -07:00
commit e456175a81
42 changed files with 3130 additions and 3262 deletions

10
.eslintrc.yml Normal file
View File

@ -0,0 +1,10 @@
root: true
extends: semistandard
rules:
indent:
- error
- 4
camelcase: off
padded-blocks: off
operator-linebreak: off
no-throw-literal: off

View File

@ -15,7 +15,7 @@ android:
- tools - tools
- tools - tools
script: script:
- npm run jshint - npm run eslint
- npm run cover - npm run cover
- npm run test-build - npm run test-build
after_script: after_script:

View File

@ -19,12 +19,12 @@
under the License. under the License.
*/ */
var shell = require('shelljs'), var shell = require('shelljs');
Q = require('q'), var Q = require('q');
path = require('path'), var path = require('path');
fs = require('fs'), var fs = require('fs');
check_reqs = require('./../templates/cordova/lib/check_reqs'), var check_reqs = require('./../templates/cordova/lib/check_reqs');
ROOT = path.join(__dirname, '..', '..'); var ROOT = path.join(__dirname, '..', '..');
var MIN_SDK_VERSION = 19; var MIN_SDK_VERSION = 19;
@ -32,14 +32,14 @@ var CordovaError = require('cordova-common').CordovaError;
var AndroidStudio = require('../templates/cordova/lib/AndroidStudio'); var AndroidStudio = require('../templates/cordova/lib/AndroidStudio');
var AndroidManifest = require('../templates/cordova/lib/AndroidManifest'); var AndroidManifest = require('../templates/cordova/lib/AndroidManifest');
function setShellFatal(value, func) { function setShellFatal (value, func) {
var oldVal = shell.config.fatal; var oldVal = shell.config.fatal;
shell.config.fatal = value; shell.config.fatal = value;
func(); func();
shell.config.fatal = oldVal; shell.config.fatal = oldVal;
} }
function getFrameworkDir(projectPath, shared) { function getFrameworkDir (projectPath, shared) {
return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib'); return shared ? path.join(ROOT, 'framework') : path.join(projectPath, 'CordovaLib');
} }
@ -62,9 +62,9 @@ function copyJsAndLibrary(projectPath, shared, projectName, isLegacy) {
// We need these files to build cordova.js if using browserify method. // We need these files to build cordova.js if using browserify method.
shell.cp('-rf', path.join(ROOT, 'cordova-js-src'), path.join(projectPath, 'platform_www')); shell.cp('-rf', path.join(ROOT, 'cordova-js-src'), path.join(projectPath, 'platform_www'));
// Don't fail if there are no old jars, because there hasn't been cordova JARs for years! // Don't fail if there are no old jars.
setShellFatal(false, function() { setShellFatal(false, function () {
shell.ls(path.join(app_path, 'libs', 'cordova-*.jar')).forEach(function(oldJar) { shell.ls(path.join(projectPath, 'libs', 'cordova-*.jar')).forEach(function (oldJar) {
console.log('Deleting ' + oldJar); console.log('Deleting ' + oldJar);
shell.rm('-f', oldJar); shell.rm('-f', oldJar);
}); });
@ -96,7 +96,7 @@ function copyJsAndLibrary(projectPath, shared, projectName, isLegacy) {
} }
} }
function extractSubProjectPaths(data) { function extractSubProjectPaths (data) {
var ret = {}; var ret = {};
var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg; var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg;
var m; var m;
@ -106,7 +106,7 @@ function extractSubProjectPaths(data) {
return Object.keys(ret); return Object.keys(ret);
} }
function writeProjectProperties(projectPath, target_api) { function writeProjectProperties (projectPath, target_api) {
var dstPath = path.join(projectPath, 'project.properties'); var dstPath = path.join(projectPath, 'project.properties');
var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties'); var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties');
var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath; var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath;
@ -114,11 +114,10 @@ function writeProjectProperties(projectPath, target_api) {
var data = fs.readFileSync(srcPath, 'utf8'); var data = fs.readFileSync(srcPath, 'utf8');
data = data.replace(/^target=.*/m, 'target=' + target_api); data = data.replace(/^target=.*/m, 'target=' + target_api);
var subProjects = extractSubProjectPaths(data); var subProjects = extractSubProjectPaths(data);
subProjects = subProjects.filter(function(p) { subProjects = subProjects.filter(function (p) {
return !(/^CordovaLib$/m.exec(p) || return !(/^CordovaLib$/m.exec(p) ||
/[\\\/]cordova-android[\\\/]framework$/m.exec(p) || /[\\/]cordova-android[\\/]framework$/m.exec(p) ||
/^(\.\.[\\\/])+framework$/m.exec(p) /^(\.\.[\\/])+framework$/m.exec(p));
);
}); });
subProjects.unshift('CordovaLib'); subProjects.unshift('CordovaLib');
data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, ''); data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, '');
@ -126,7 +125,7 @@ function writeProjectProperties(projectPath, target_api) {
data += '\n'; data += '\n';
} }
for (var i = 0; i < subProjects.length; ++i) { for (var i = 0; i < subProjects.length; ++i) {
data += 'android.library.reference.' + (i+1) + '=' + subProjects[i] + '\n'; data += 'android.library.reference.' + (i + 1) + '=' + subProjects[i] + '\n';
} }
fs.writeFileSync(dstPath, data); fs.writeFileSync(dstPath, data);
} }
@ -151,7 +150,7 @@ function copyBuildRules(projectPath, isLegacy) {
} }
} }
function copyScripts(projectPath) { function copyScripts (projectPath) {
var bin = path.join(ROOT, 'bin'); var bin = path.join(ROOT, 'bin');
var srcScriptsDir = path.join(bin, 'templates', 'cordova'); var srcScriptsDir = path.join(bin, 'templates', 'cordova');
var destScriptsDir = path.join(projectPath, 'cordova'); var destScriptsDir = path.join(projectPath, 'cordova');
@ -178,17 +177,17 @@ function copyScripts(projectPath) {
* Returns a promise, fulfilled if the package name is acceptable; rejected * Returns a promise, fulfilled if the package name is acceptable; rejected
* otherwise. * otherwise.
*/ */
function validatePackageName(package_name) { function validatePackageName (package_name) {
//Make the package conform to Java package types // Make the package conform to Java package types
//http://developer.android.com/guide/topics/manifest/manifest-element.html#package // http://developer.android.com/guide/topics/manifest/manifest-element.html#package
//Enforce underscore limitation // Enforce underscore limitation
var msg = 'Error validating package name. '; var msg = 'Error validating package name. ';
if (!/^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(package_name)) { if (!/^[a-zA-Z][a-zA-Z0-9_]+(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(package_name)) {
return Q.reject(new CordovaError(msg + 'Package name must look like: com.company.Name')); return Q.reject(new CordovaError(msg + 'Package name must look like: com.company.Name'));
} }
//Class is a reserved word // Class is a reserved word
if(/\b[Cc]lass\b/.test(package_name)) { if (/\b[Cc]lass\b/.test(package_name)) {
return Q.reject(new CordovaError(msg + '"class" is a reserved word')); return Q.reject(new CordovaError(msg + '"class" is a reserved word'));
} }
@ -200,19 +199,19 @@ function validatePackageName(package_name) {
* Returns a promise, fulfilled if the project name is acceptable; rejected * Returns a promise, fulfilled if the project name is acceptable; rejected
* otherwise. * otherwise.
*/ */
function validateProjectName(project_name) { function validateProjectName (project_name) {
var msg = 'Error validating project name. '; var msg = 'Error validating project name. ';
//Make sure there's something there // Make sure there's something there
if (project_name === '') { if (project_name === '') {
return Q.reject(new CordovaError(msg + 'Project name cannot be empty')); return Q.reject(new CordovaError(msg + 'Project name cannot be empty'));
} }
//Enforce stupid name error // Enforce stupid name error
if (project_name === 'CordovaActivity') { if (project_name === 'CordovaActivity') {
return Q.reject(new CordovaError(msg + 'Project name cannot be CordovaActivity')); return Q.reject(new CordovaError(msg + 'Project name cannot be CordovaActivity'));
} }
//Classes in Java don't begin with numbers // Classes in Java don't begin with numbers
if (/^[0-9]/.test(project_name)) { if (/^[0-9]/.test(project_name)) {
return Q.reject(new CordovaError(msg + 'Project name must not begin with a number')); return Q.reject(new CordovaError(msg + 'Project name must not begin with a number'));
} }
@ -238,29 +237,29 @@ function validateProjectName(project_name) {
* *
* @return {Promise<String>} Directory where application has been created * @return {Promise<String>} Directory where application has been created
*/ */
exports.create = function(project_path, config, options, events) { exports.create = function (project_path, config, options, events) {
options = options || {}; options = options || {};
// Set default values for path, package and name // Set default values for path, package and name
project_path = path.relative(process.cwd(), (project_path || 'CordovaExample')); project_path = path.relative(process.cwd(), (project_path || 'CordovaExample'));
// Check if project already exists // Check if project already exists
if(fs.existsSync(project_path)) { if (fs.existsSync(project_path)) {
return Q.reject(new CordovaError('Project already exists! Delete and recreate')); return Q.reject(new CordovaError('Project already exists! Delete and recreate'));
} }
var package_name = config.packageName() || 'my.cordova.project'; var package_name = config.packageName() || 'my.cordova.project';
var project_name = config.name() ? var project_name = config.name() ?
config.name().replace(/[^\w.]/g,'_') : 'CordovaExample'; config.name().replace(/[^\w.]/g, '_') : 'CordovaExample';
var safe_activity_name = config.android_activityName() || options.activityName || 'MainActivity'; var safe_activity_name = config.android_activityName() || options.activityName || 'MainActivity';
var target_api = check_reqs.get_target(); var target_api = check_reqs.get_target();
//Make the package conform to Java package types // Make the package conform to Java package types
return validatePackageName(package_name) return validatePackageName(package_name)
.then(function() { .then(function () {
validateProjectName(project_name); validateProjectName(project_name);
}).then(function() { }).then(function () {
// Log the given values for the project // Log the given values for the project
events.emit('log', 'Creating Cordova project for the Android platform:'); events.emit('log', 'Creating Cordova project for the Android platform:');
events.emit('log', '\tPath: ' + project_path); events.emit('log', '\tPath: ' + project_path);
@ -324,9 +323,9 @@ exports.create = function(project_path, config, options, events) {
}).thenResolve(project_path); }).thenResolve(project_path);
}; };
function generateDoneMessage(type, link) { function generateDoneMessage (type, link) {
var pkg = require('../../package'); var pkg = require('../../package');
var msg = 'Android project ' + (type == 'update' ? 'updated ' : 'created ') + 'with ' + pkg.name + '@' + pkg.version; var msg = 'Android project ' + (type === 'update' ? 'updated ' : 'created ') + 'with ' + pkg.name + '@' + pkg.version;
if (link) { if (link) {
msg += ' and has a linked CordovaLib'; msg += ' and has a linked CordovaLib';
} }
@ -334,11 +333,11 @@ function generateDoneMessage(type, link) {
} }
// Returns a promise. // Returns a promise.
exports.update = function(projectPath, options, events) { exports.update = function (projectPath, options, events) {
options = options || {}; options = options || {};
return Q() return Q()
.then(function() { .then(function () {
var isAndroidStudio = AndroidStudio.isAndroidStudioProject(projectPath); var isAndroidStudio = AndroidStudio.isAndroidStudioProject(projectPath);
var isLegacy = !isAndroidStudio; var isLegacy = !isAndroidStudio;
@ -353,15 +352,15 @@ exports.update = function(projectPath, options, events) {
builder = 'gradle'; builder = 'gradle';
} }
if (Number(manifest.getMinSdkVersion()) < MIN_SDK_VERSION) { if (Number(manifest.getMinSdkVersion()) < MIN_SDK_VERSION) {
events.emit('verbose', 'Updating minSdkVersion to ' + MIN_SDK_VERSION + ' in AndroidManifest.xml'); events.emit('verbose', 'Updating minSdkVersion to ' + MIN_SDK_VERSION + ' in AndroidManifest.xml');
manifest.setMinSdkVersion(MIN_SDK_VERSION); manifest.setMinSdkVersion(MIN_SDK_VERSION);
} }
manifest.setDebuggable(false).write(); manifest.setDebuggable(false).write();
var projectName = manifest.getActivity().getName(); var projectName = manifest.getActivity().getName();
var target_api = check_reqs.get_target(); var target_api = check_reqs.get_target();
copyJsAndLibrary(projectPath, options.link, projectName, isLegacy); copyJsAndLibrary(projectPath, options.link, projectName, isLegacy);
copyScripts(projectPath); copyScripts(projectPath);
@ -372,7 +371,6 @@ exports.update = function(projectPath, options, events) {
}).thenResolve(projectPath); }).thenResolve(projectPath);
}; };
// For testing // For testing
exports.validatePackageName = validatePackageName; exports.validatePackageName = validatePackageName;
exports.validateProjectName = validateProjectName; exports.validateProjectName = validateProjectName;

View File

@ -29,8 +29,7 @@ var selfEvents = require('cordova-common').events;
var PLATFORM = 'android'; var PLATFORM = 'android';
function setupEvents (externalEventEmitter) {
function setupEvents(externalEventEmitter) {
if (externalEventEmitter) { if (externalEventEmitter) {
// This will make the platform internal events visible outside // This will make the platform internal events visible outside
selfEvents.forwardEventsTo(externalEventEmitter); selfEvents.forwardEventsTo(externalEventEmitter);
@ -43,7 +42,6 @@ function setupEvents(externalEventEmitter) {
return selfEvents; return selfEvents;
} }
/** /**
* Class, that acts as abstraction over particular platform. Encapsulates the * Class, that acts as abstraction over particular platform. Encapsulates the
* platform's properties and methods. * platform's properties and methods.
@ -55,7 +53,7 @@ function setupEvents(externalEventEmitter) {
* *
* * platform: String that defines a platform name. * * platform: String that defines a platform name.
*/ */
function Api(platform, platformRootDir, events) { function Api (platform, platformRootDir, events) {
this.platform = PLATFORM; this.platform = PLATFORM;
this.root = path.resolve(__dirname, '..'); this.root = path.resolve(__dirname, '..');
this.builder = 'gradle'; this.builder = 'gradle';
@ -82,16 +80,16 @@ function Api(platform, platformRootDir, events) {
// XXX Override some locations for Android Studio projects // XXX Override some locations for Android Studio projects
if(AndroidStudio.isAndroidStudioProject(self.root) === true) { if(AndroidStudio.isAndroidStudioProject(self.root) === true) {
selfEvents.emit('log', 'Android Studio project detected'); selfEvents.emit('log', 'Android Studio project detected');
this.builder='studio'; this.builder='studio';
this.android_studio = true; this.android_studio = true;
this.locations.configXml = path.join(self.root, 'app/src/main/res/xml/config.xml'); 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/values/strings.xml'); this.locations.strings = path.join(self.root, 'app/src/main/res/values/strings.xml');
this.locations.manifest = path.join(self.root, 'app/src/main/AndroidManifest.xml'); this.locations.manifest = path.join(self.root, 'app/src/main/AndroidManifest.xml');
//We could have Java Source, we could have other languages //We could have Java Source, we could have other languages
this.locations.javaSrc = path.join(self.root, 'app/src/main/java/'); this.locations.javaSrc = path.join(self.root, 'app/src/main/java/');
this.locations.www = path.join(self.root, 'app/src/main/assets/www'); this.locations.www = path.join(self.root, 'app/src/main/assets/www');
this.locations.res = path.join(self.root, 'app/src/main/res'); this.locations.res = path.join(self.root, 'app/src/main/res');
} }
} }
@ -117,16 +115,13 @@ Api.createPlatform = function (destination, config, options, events) {
events = setupEvents(events); events = setupEvents(events);
var result; var result;
try { try {
result = require('../../lib/create') result = require('../../lib/create').create(destination, config, options, events).then(function (destination) {
.create(destination, config, options, events)
.then(function (destination) {
var PlatformApi = require(path.resolve(destination, 'cordova/Api')); var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
return new PlatformApi(PLATFORM, destination, events); return new PlatformApi(PLATFORM, destination, events);
}); });
} } catch (e) {
catch (e) { events.emit('error', 'createPlatform is not callable from the android project API.');
events.emit('error','createPlatform is not callable from the android project API.'); throw (e);
throw(e);
} }
return result; return result;
}; };
@ -151,16 +146,13 @@ Api.updatePlatform = function (destination, options, events) {
events = setupEvents(events); events = setupEvents(events);
var result; var result;
try { try {
result = require('../../lib/create') result = require('../../lib/create').update(destination, options, events).then(function (destination) {
.update(destination, options, events)
.then(function (destination) {
var PlatformApi = require(path.resolve(destination, 'cordova/Api')); var PlatformApi = require(path.resolve(destination, 'cordova/Api'));
return new PlatformApi('android', destination, events); return new PlatformApi('android', destination, events);
}); });
} } catch (e) {
catch (e) { events.emit('error', 'updatePlatform is not callable from the android project API, you will need to do this manually.');
events.emit('error','updatePlatform is not callable from the android project API, you will need to do this manually.'); throw (e);
throw(e);
} }
return result; return result;
}; };
@ -231,41 +223,35 @@ Api.prototype.addPlugin = function (plugin, installOptions) {
installOptions.variables.PACKAGE_NAME = project.getPackageName(); installOptions.variables.PACKAGE_NAME = project.getPackageName();
} }
if(this.android_studio === true) { if (this.android_studio === true) {
installOptions.android_studio = true; installOptions.android_studio = true;
} }
return Q() return Q().then(function () {
.then(function () { // CB-11964: Do a clean when installing the plugin code to get around
//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
//the Gradle bug introduced by the Android Gradle Plugin Version 2.2 // TODO: Delete when the next version of Android Gradle plugin comes out
//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.
// Since clean doesn't just clean the build, it also wipes out www, we need // Do some basic argument parsing
// to pass additional options. var opts = {};
// Do some basic argument parsing // Skip cleaning prepared files when not invoking via cordova CLI.
var opts = {}; opts.noPrepare = true;
// Skip cleaning prepared files when not invoking via cordova CLI. if (!AndroidStudio.isAndroidStudioProject(self.root) && !project.isClean()) {
opts.noPrepare = true; return self.clean(opts);
}
if(!AndroidStudio.isAndroidStudioProject(self.root) && !project.isClean()) { }).then(function () {
return self.clean(opts); return PluginManager.get(self.platform, self.locations, project).addPlugin(plugin, installOptions);
} }).then(function () {
}) if (plugin.getFrameworks(this.platform).length === 0) return;
.then(function () { selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
return PluginManager.get(self.platform, self.locations, project) //This should pick the correct builder, not just get gradle
.addPlugin(plugin, installOptions); require('./lib/builders/builders').getBuilder(this.builder).prepBuildFiles();
}) }.bind(this))
.then(function () { // CB-11022 Return truthy value to prevent running prepare after
if (plugin.getFrameworks(this.platform).length === 0) return;
selfEvents.emit('verbose', 'Updating build files since android plugin contained <framework>');
//This should pick the correct builder, not just get gradle
require('./lib/builders/builders').getBuilder(this.builder).prepBuildFiles();
}.bind(this))
// CB-11022 Return truthy value to prevent running prepare after
.thenResolve(true); .thenResolve(true);
}; };
@ -285,9 +271,9 @@ Api.prototype.addPlugin = function (plugin, installOptions) {
Api.prototype.removePlugin = function (plugin, uninstallOptions) { Api.prototype.removePlugin = function (plugin, uninstallOptions) {
var project = AndroidProject.getProjectFile(this.root); var project = AndroidProject.getProjectFile(this.root);
if(uninstallOptions && uninstallOptions.usePlatformWww === true && this.android_studio === true) { if (uninstallOptions && uninstallOptions.usePlatformWww === true && this.android_studio === true) {
uninstallOptions.usePlatformWww = false; uninstallOptions.usePlatformWww = false;
uninstallOptions.android_studio = true; uninstallOptions.android_studio = true;
} }
return PluginManager.get(this.platform, this.locations, project) return PluginManager.get(this.platform, this.locations, project)
@ -351,11 +337,9 @@ Api.prototype.build = function (buildOptions) {
var self = this; var self = this;
if(this.android_studio) if(this.android_studio)
buildOptions.studio = true; buildOptions.studio = true;
return require('./lib/check_reqs').run() return require('./lib/check_reqs').run() .then(function () {
.then(function () {
return require('./lib/build').run.call(self, buildOptions); return require('./lib/build').run.call(self, buildOptions);
}) }).then(function (buildResults) {
.then(function (buildResults) {
// Cast build result to array of build artifacts // Cast build result to array of build artifacts
return buildResults.apkPaths.map(function (apkPath) { return buildResults.apkPaths.map(function (apkPath) {
return { return {
@ -380,10 +364,9 @@ Api.prototype.build = function (buildOptions) {
* @return {Promise} A promise either fulfilled if package was built and ran * @return {Promise} A promise either fulfilled if package was built and ran
* successfully, or rejected with CordovaError. * successfully, or rejected with CordovaError.
*/ */
Api.prototype.run = function(runOptions) { Api.prototype.run = function (runOptions) {
var self = this; var self = this;
return require('./lib/check_reqs').run() return require('./lib/check_reqs').run().then(function () {
.then(function () {
return require('./lib/run').run.call(self, runOptions); return require('./lib/run').run.call(self, runOptions);
}); });
}; };
@ -395,19 +378,15 @@ Api.prototype.run = function(runOptions) {
* @return {Promise} Return a promise either fulfilled, or rejected with * @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError. * CordovaError.
*/ */
Api.prototype.clean = function(cleanOptions) { Api.prototype.clean = function (cleanOptions) {
var self = this; var self = this;
return require('./lib/check_reqs').run() return require('./lib/check_reqs').run().then(function () {
.then(function () { return require('./lib/build').runClean.call(self, cleanOptions);
return require('./lib/build').runClean.call(self, cleanOptions); }).then(function () {
}) return require('./lib/prepare').clean.call(self, cleanOptions);
.then(function () { });
return require('./lib/prepare').clean.call(self, cleanOptions);
});
}; };
/** /**
* Performs a requirements check for current platform. Each platform defines its * Performs a requirements check for current platform. Each platform defines its
* own set of requirements, which should be resolved before platform can be * own set of requirements, which should be resolved before platform can be
@ -416,7 +395,7 @@ Api.prototype.clean = function(cleanOptions) {
* @return {Promise<Requirement[]>} Promise, resolved with set of Requirement * @return {Promise<Requirement[]>} Promise, resolved with set of Requirement
* objects for current platform. * objects for current platform.
*/ */
Api.prototype.requirements = function() { Api.prototype.requirements = function () {
return require('./lib/check_reqs').check_all(); return require('./lib/check_reqs').check_all();
}; };

View File

@ -25,11 +25,11 @@ var CordovaError = require('cordova-common').CordovaError;
var Adb = {}; var Adb = {};
function isDevice(line) { function isDevice (line) {
return line.match(/\w+\tdevice/) && !line.match(/emulator/); return line.match(/\w+\tdevice/) && !line.match(/emulator/);
} }
function isEmulator(line) { function isEmulator (line) {
return line.match(/device/) && line.match(/emulator/); return line.match(/device/) && line.match(/emulator/);
} }
@ -44,8 +44,7 @@ function isEmulator(line) {
* devices/emulators * devices/emulators
*/ */
Adb.devices = function (opts) { Adb.devices = function (opts) {
return spawn('adb', ['devices'], {cwd: os.tmpdir()}) return spawn('adb', ['devices'], {cwd: os.tmpdir()}).then(function (output) {
.then(function(output) {
return output.split('\n').filter(function (line) { return output.split('\n').filter(function (line) {
// Filter out either real devices or emulators, depending on options // Filter out either real devices or emulators, depending on options
return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line); return (line && opts && opts.emulators) ? isEmulator(line) : isDevice(line);
@ -59,8 +58,7 @@ Adb.install = function (target, packagePath, opts) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...'); events.emit('verbose', 'Installing apk ' + packagePath + ' on target ' + target + '...');
var args = ['-s', target, 'install']; var args = ['-s', target, 'install'];
if (opts && opts.replace) args.push('-r'); if (opts && opts.replace) args.push('-r');
return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()}) return spawn('adb', args.concat(packagePath), {cwd: os.tmpdir()}).then(function (output) {
.then(function(output) {
// 'adb install' seems to always returns no error, even if installation fails // 'adb install' seems to always returns no error, even if installation fails
// so we catching output to detect installation failure // so we catching output to detect installation failure
if (output.match(/Failure/)) { if (output.match(/Failure/)) {
@ -86,8 +84,7 @@ Adb.shell = function (target, shellCommand) {
events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...'); events.emit('verbose', 'Running adb shell command "' + shellCommand + '" on target ' + target + '...');
var args = ['-s', target, 'shell']; var args = ['-s', target, 'shell'];
shellCommand = shellCommand.split(/\s+/); shellCommand = shellCommand.split(/\s+/);
return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()}) return spawn('adb', args.concat(shellCommand), {cwd: os.tmpdir()}).catch(function (output) {
.catch(function (output) {
return Q.reject(new CordovaError('Failed to execute shell command "' + return Q.reject(new CordovaError('Failed to execute shell command "' +
shellCommand + '"" on device: ' + output)); shellCommand + '"" on device: ' + output));
}); });
@ -95,8 +92,7 @@ Adb.shell = function (target, shellCommand) {
Adb.start = function (target, activityName) { Adb.start = function (target, activityName) {
events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...'); events.emit('verbose', 'Starting application "' + activityName + '" on target ' + target + '...');
return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName) return Adb.shell(target, 'am start -W -a android.intent.action.MAIN -n' + activityName).catch(function (output) {
.catch(function (output) {
return Q.reject(new CordovaError('Failed to start application "' + return Q.reject(new CordovaError('Failed to start application "' +
activityName + '"" on device: ' + output)); activityName + '"" on device: ' + output));
}); });

View File

@ -19,12 +19,12 @@
var fs = require('fs'); var fs = require('fs');
var et = require('elementtree'); var et = require('elementtree');
var xml= require('cordova-common').xmlHelpers; var xml = require('cordova-common').xmlHelpers;
var DEFAULT_ORIENTATION = 'default'; var DEFAULT_ORIENTATION = 'default';
/** Wraps an AndroidManifest file */ /** Wraps an AndroidManifest file */
function AndroidManifest(path) { function AndroidManifest (path) {
this.path = path; this.path = path;
this.doc = xml.parseElementtreeSync(path); this.doc = xml.parseElementtreeSync(path);
if (this.doc.getroot().tag !== 'manifest') { if (this.doc.getroot().tag !== 'manifest') {
@ -32,38 +32,38 @@ function AndroidManifest(path) {
} }
} }
AndroidManifest.prototype.getVersionName = function() { AndroidManifest.prototype.getVersionName = function () {
return this.doc.getroot().attrib['android:versionName']; return this.doc.getroot().attrib['android:versionName'];
}; };
AndroidManifest.prototype.setVersionName = function(versionName) { AndroidManifest.prototype.setVersionName = function (versionName) {
this.doc.getroot().attrib['android:versionName'] = versionName; this.doc.getroot().attrib['android:versionName'] = versionName;
return this; return this;
}; };
AndroidManifest.prototype.getVersionCode = function() { AndroidManifest.prototype.getVersionCode = function () {
return this.doc.getroot().attrib['android:versionCode']; return this.doc.getroot().attrib['android:versionCode'];
}; };
AndroidManifest.prototype.setVersionCode = function(versionCode) { AndroidManifest.prototype.setVersionCode = function (versionCode) {
this.doc.getroot().attrib['android:versionCode'] = versionCode; this.doc.getroot().attrib['android:versionCode'] = versionCode;
return this; return this;
}; };
AndroidManifest.prototype.getPackageId = function() { AndroidManifest.prototype.getPackageId = function () {
/*jshint -W069 */ /* jshint -W069 */
return this.doc.getroot().attrib['package']; return this.doc.getroot().attrib['package'];
/*jshint +W069 */ /* jshint +W069 */
}; };
AndroidManifest.prototype.setPackageId = function(pkgId) { AndroidManifest.prototype.setPackageId = function (pkgId) {
/*jshint -W069 */ /* jshint -W069 */
this.doc.getroot().attrib['package'] = pkgId; this.doc.getroot().attrib['package'] = pkgId;
/*jshint +W069 */ /* jshint +W069 */
return this; return this;
}; };
AndroidManifest.prototype.getActivity = function() { AndroidManifest.prototype.getActivity = function () {
var activity = this.doc.getroot().find('./application/activity'); var activity = this.doc.getroot().find('./application/activity');
return { return {
getName: function () { getName: function () {
@ -102,17 +102,16 @@ AndroidManifest.prototype.getActivity = function() {
}; };
}; };
['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion'] ['minSdkVersion', 'maxSdkVersion', 'targetSdkVersion'].forEach(function (sdkPrefName) {
.forEach(function(sdkPrefName) {
// Copy variable reference to avoid closure issues // Copy variable reference to avoid closure issues
var prefName = sdkPrefName; var prefName = sdkPrefName;
AndroidManifest.prototype['get' + capitalize(prefName)] = function() { AndroidManifest.prototype['get' + capitalize(prefName)] = function () {
var usesSdk = this.doc.getroot().find('./uses-sdk'); var usesSdk = this.doc.getroot().find('./uses-sdk');
return usesSdk && usesSdk.attrib['android:' + prefName]; return usesSdk && usesSdk.attrib['android:' + prefName];
}; };
AndroidManifest.prototype['set' + capitalize(prefName)] = function(prefValue) { AndroidManifest.prototype['set' + capitalize(prefName)] = function (prefValue) {
var usesSdk = this.doc.getroot().find('./uses-sdk'); var usesSdk = this.doc.getroot().find('./uses-sdk');
if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first if (!usesSdk && prefValue) { // if there is no required uses-sdk element, we should create it first
@ -128,11 +127,11 @@ AndroidManifest.prototype.getActivity = function() {
}; };
}); });
AndroidManifest.prototype.getDebuggable = function() { AndroidManifest.prototype.getDebuggable = function () {
return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true'; return this.doc.getroot().find('./application').attrib['android:debuggable'] === 'true';
}; };
AndroidManifest.prototype.setDebuggable = function(value) { AndroidManifest.prototype.setDebuggable = function (value) {
var application = this.doc.getroot().find('./application'); var application = this.doc.getroot().find('./application');
if (value) { if (value) {
application.attrib['android:debuggable'] = 'true'; application.attrib['android:debuggable'] = 'true';
@ -150,7 +149,7 @@ AndroidManifest.prototype.setDebuggable = function(value) {
* @param {String} [destPath] File to write manifest to. If omitted, * @param {String} [destPath] File to write manifest to. If omitted,
* manifest will be written to file it has been read from. * manifest will be written to file it has been read from.
*/ */
AndroidManifest.prototype.write = function(destPath) { AndroidManifest.prototype.write = function (destPath) {
fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8'); fs.writeFileSync(destPath || this.path, this.doc.write({indent: 4}), 'utf-8');
}; };

View File

@ -26,16 +26,15 @@ var pluginHandlers = require('./pluginHandlers');
var projectFileCache = {}; var projectFileCache = {};
function addToPropertyList(projectProperties, key, value) { function addToPropertyList (projectProperties, key, value) {
var i = 1; var i = 1;
while (projectProperties.get(key + '.' + i)) while (projectProperties.get(key + '.' + i)) { i++; }
i++;
projectProperties.set(key + '.' + i, value); projectProperties.set(key + '.' + i, value);
projectProperties.dirty = true; projectProperties.dirty = true;
} }
function removeFromPropertyList(projectProperties, key, value) { function removeFromPropertyList (projectProperties, key, value) {
var i = 1; var i = 1;
var currentValue; var currentValue;
while ((currentValue = projectProperties.get(key + '.' + i))) { while ((currentValue = projectProperties.get(key + '.' + i))) {
@ -54,18 +53,18 @@ function removeFromPropertyList(projectProperties, key, value) {
function getRelativeLibraryPath (parentDir, subDir) { function getRelativeLibraryPath (parentDir, subDir) {
var libraryPath = path.relative(parentDir, subDir); var libraryPath = path.relative(parentDir, subDir);
return (path.sep == '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath; return (path.sep === '\\') ? libraryPath.replace(/\\/g, '/') : libraryPath;
} }
function AndroidProject(projectDir) { function AndroidProject (projectDir) {
this._propertiesEditors = {}; this._propertiesEditors = {};
this._subProjectDirs = {}; this._subProjectDirs = {};
this._dirty = false; this._dirty = false;
this.projectDir = projectDir; this.projectDir = projectDir;
this.platformWww = path.join(this.projectDir, 'platform_www'); this.platformWww = path.join(this.projectDir, 'platform_www');
this.www = path.join(this.projectDir, 'assets/www'); this.www = path.join(this.projectDir, 'assets/www');
if(AndroidStudio.isAndroidStudioProject(projectDir) === true) { if (AndroidStudio.isAndroidStudioProject(projectDir) === true) {
this.www = path.join(this.projectDir, 'app/src/main/assets/www'); this.www = path.join(this.projectDir, 'app/src/main/assets/www');
} }
} }
@ -92,15 +91,15 @@ AndroidProject.purgeCache = function (projectDir) {
* *
* @return {String} The name of the package * @return {String} The name of the package
*/ */
AndroidProject.prototype.getPackageName = function() { AndroidProject.prototype.getPackageName = function () {
var manifestPath = path.join(this.projectDir, 'AndroidManifest.xml'); var manifestPath = path.join(this.projectDir, 'AndroidManifest.xml');
if(AndroidStudio.isAndroidStudioProject(this.projectDir) === true) { if (AndroidStudio.isAndroidStudioProject(this.projectDir) === true) {
manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml'); manifestPath = path.join(this.projectDir, 'app/src/main/AndroidManifest.xml');
} }
return new AndroidManifest(manifestPath).getPackageId(); return new AndroidManifest(manifestPath).getPackageId();
}; };
AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, src) { AndroidProject.prototype.getCustomSubprojectRelativeDir = function (plugin_id, src) {
// All custom subprojects are prefixed with the last portion of the package id. // 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. // This is to avoid collisions when opening multiple projects in Eclipse that have subprojects with the same name.
var packageName = this.getPackageName(); var packageName = this.getPackageName();
@ -110,7 +109,7 @@ AndroidProject.prototype.getCustomSubprojectRelativeDir = function(plugin_id, sr
return subRelativeDir; return subRelativeDir;
}; };
AndroidProject.prototype.addSubProject = function(parentDir, subDir) { AndroidProject.prototype.addSubProject = function (parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties'); var parentProjectFile = path.resolve(parentDir, 'project.properties');
var subProjectFile = path.resolve(subDir, 'project.properties'); var subProjectFile = path.resolve(subDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile); var parentProperties = this._getPropertiesFile(parentProjectFile);
@ -126,7 +125,7 @@ AndroidProject.prototype.addSubProject = function(parentDir, subDir) {
this._dirty = true; this._dirty = true;
}; };
AndroidProject.prototype.removeSubProject = function(parentDir, subDir) { AndroidProject.prototype.removeSubProject = function (parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties'); var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile); var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir)); removeFromPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir));
@ -134,35 +133,35 @@ AndroidProject.prototype.removeSubProject = function(parentDir, subDir) {
this._dirty = true; this._dirty = true;
}; };
AndroidProject.prototype.addGradleReference = function(parentDir, subDir) { AndroidProject.prototype.addGradleReference = function (parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties'); var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile); var parentProperties = this._getPropertiesFile(parentProjectFile);
addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir)); addToPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true; this._dirty = true;
}; };
AndroidProject.prototype.removeGradleReference = function(parentDir, subDir) { AndroidProject.prototype.removeGradleReference = function (parentDir, subDir) {
var parentProjectFile = path.resolve(parentDir, 'project.properties'); var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile); var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir)); removeFromPropertyList(parentProperties, 'cordova.gradle.include', getRelativeLibraryPath(parentDir, subDir));
this._dirty = true; this._dirty = true;
}; };
AndroidProject.prototype.addSystemLibrary = function(parentDir, value) { AndroidProject.prototype.addSystemLibrary = function (parentDir, value) {
var parentProjectFile = path.resolve(parentDir, 'project.properties'); var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile); var parentProperties = this._getPropertiesFile(parentProjectFile);
addToPropertyList(parentProperties, 'cordova.system.library', value); addToPropertyList(parentProperties, 'cordova.system.library', value);
this._dirty = true; this._dirty = true;
}; };
AndroidProject.prototype.removeSystemLibrary = function(parentDir, value) { AndroidProject.prototype.removeSystemLibrary = function (parentDir, value) {
var parentProjectFile = path.resolve(parentDir, 'project.properties'); var parentProjectFile = path.resolve(parentDir, 'project.properties');
var parentProperties = this._getPropertiesFile(parentProjectFile); var parentProperties = this._getPropertiesFile(parentProjectFile);
removeFromPropertyList(parentProperties, 'cordova.system.library', value); removeFromPropertyList(parentProperties, 'cordova.system.library', value);
this._dirty = true; this._dirty = true;
}; };
AndroidProject.prototype.write = function() { AndroidProject.prototype.write = function () {
if (!this._dirty) { if (!this._dirty) {
return; return;
} }
@ -201,9 +200,9 @@ AndroidProject.prototype.getUninstaller = function (type) {
* This checks if an Android project is clean or has old build artifacts * This checks if an Android project is clean or has old build artifacts
*/ */
AndroidProject.prototype.isClean = function() { AndroidProject.prototype.isClean = function () {
var build_path = path.join(this.projectDir, 'build'); var build_path = path.join(this.projectDir, 'build');
//If the build directory doesn't exist, it's clean // If the build directory doesn't exist, it's clean
return !(fs.existsSync(build_path)); return !(fs.existsSync(build_path));
}; };

View File

@ -4,7 +4,7 @@
* @param {String} root Root folder of the project * @param {String} root Root folder of the project
*/ */
/*jshint esnext: false */ /* jshint esnext: false */
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
@ -18,21 +18,21 @@ module.exports.isAndroidStudioProject = function isAndroidStudioProject(root) {
var isEclipse = false; var isEclipse = false;
var isAS = true; var isAS = true;
if(!fs.existsSync(root)) { if (!fs.existsSync(root)) {
throw new CordovaError('AndroidStudio.js:inAndroidStudioProject root does not exist: ' + root); throw new CordovaError('AndroidStudio.js:inAndroidStudioProject root does not exist: ' + root);
} }
// if any of the following exists, then we are not an ASProj // if any of the following exists, then we are not an ASProj
eclipseFiles.forEach(function(file) { eclipseFiles.forEach(function (file) {
if(fs.existsSync(path.join(root, file))) { if (fs.existsSync(path.join(root, file))) {
isEclipse = true; isEclipse = true;
} }
}); });
// if it is NOT an eclipse project, check that all required files exist // if it is NOT an eclipse project, check that all required files exist
if(!isEclipse) { if (!isEclipse) {
androidStudioFiles.forEach(function(file){ androidStudioFiles.forEach(function (file) {
if(!fs.existsSync(path.join(root, file))) { if (!fs.existsSync(path.join(root, file))) {
console.log('missing file :: ' + file); console.log('missing file :: ' + file);
isAS = false; isAS = false;
} }

View File

@ -17,8 +17,8 @@
under the License. under the License.
*/ */
var Q = require('q'), var Q = require('q');
superspawn = require('cordova-common').superspawn; var superspawn = require('cordova-common').superspawn;
var suffix_number_regex = /(\d+)$/; var suffix_number_regex = /(\d+)$/;
// Used for sorting Android targets, example strings to sort: // Used for sorting Android targets, example strings to sort:
@ -29,7 +29,7 @@ var suffix_number_regex = /(\d+)$/;
// The idea is to sort based on largest "suffix" number - meaning the bigger // The idea is to sort based on largest "suffix" number - meaning the bigger
// the number at the end, the more recent the target, the closer to the // the number at the end, the more recent the target, the closer to the
// start of the array. // start of the array.
function sort_by_largest_numerical_suffix(a, b) { function sort_by_largest_numerical_suffix (a, b) {
var suffix_a = a.match(suffix_number_regex); var suffix_a = a.match(suffix_number_regex);
var suffix_b = b.match(suffix_number_regex); var suffix_b = b.match(suffix_number_regex);
if (suffix_a && suffix_b) { if (suffix_a && suffix_b) {
@ -43,9 +43,8 @@ function sort_by_largest_numerical_suffix(a, b) {
} }
} }
module.exports.print_newest_available_sdk_target = function() { module.exports.print_newest_available_sdk_target = function () {
return module.exports.list_targets() return module.exports.list_targets().then(function (targets) {
.then(function(targets) {
targets.sort(sort_by_largest_numerical_suffix); targets.sort(sort_by_largest_numerical_suffix);
console.log(targets[0]); console.log(targets[0]);
}); });
@ -66,38 +65,34 @@ module.exports.version_string_to_api_level = {
'7.1.1': 25 '7.1.1': 25
}; };
function parse_targets(output) { function parse_targets (output) {
var target_out = output.split('\n'); var target_out = output.split('\n');
var targets = []; var targets = [];
for (var i = target_out.length - 1; i >= 0; i--) { for (var i = target_out.length - 1; i >= 0; i--) {
if(target_out[i].match(/id:/)) { // if "id:" is in the line... if (target_out[i].match(/id:/)) { // if "id:" is in the line...
targets.push(target_out[i].match(/"(.+)"/)[1]); //.. match whatever is in quotes. targets.push(target_out[i].match(/"(.+)"/)[1]); // .. match whatever is in quotes.
} }
} }
return targets; return targets;
} }
module.exports.list_targets_with_android = function() { module.exports.list_targets_with_android = function () {
return superspawn.spawn('android', ['list', 'target']) return superspawn.spawn('android', ['list', 'target']).then(parse_targets);
.then(parse_targets);
}; };
module.exports.list_targets_with_avdmanager = function() { module.exports.list_targets_with_avdmanager = function () {
return superspawn.spawn('avdmanager', ['list', 'target']) return superspawn.spawn('avdmanager', ['list', 'target']).then(parse_targets);
.then(parse_targets);
}; };
module.exports.list_targets = function() { module.exports.list_targets = function () {
return module.exports.list_targets_with_avdmanager() return module.exports.list_targets_with_avdmanager().catch(function (err) {
.catch(function(err) {
// If there's an error, like avdmanager could not be found, we can try // If there's an error, like avdmanager could not be found, we can try
// as a last resort, to run `android`, in case this is a super old // as a last resort, to run `android`, in case this is a super old
// SDK installation. // SDK installation.
if (err && (err.code == 'ENOENT' || (err.stderr && err.stderr.match(/not recognized/)))) { if (err && (err.code === 'ENOENT' || (err.stderr && err.stderr.match(/not recognized/)))) {
return module.exports.list_targets_with_android(); return module.exports.list_targets_with_android();
} else throw err; } else throw err;
}) }).then(function (targets) {
.then(function(targets) {
if (targets.length === 0) { if (targets.length === 0) {
return Q.reject(new Error('No android targets (SDKs) installed!')); return Q.reject(new Error('No android targets (SDKs) installed!'));
} }

View File

@ -19,10 +19,10 @@
under the License. under the License.
*/ */
var Q = require('q'), var Q = require('q');
path = require('path'), var path = require('path');
fs = require('fs'), var fs = require('fs');
nopt = require('nopt'); var nopt = require('nopt');
var Adb = require('./Adb'); var Adb = require('./Adb');
@ -31,7 +31,7 @@ var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn; var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
function parseOpts(options, resolvedTarget, projectRoot) { function parseOpts (options, resolvedTarget, projectRoot) {
options = options || {}; options = options || {};
options.argv = nopt({ options.argv = nopt({
gradle: Boolean, gradle: Boolean,
@ -64,11 +64,9 @@ function parseOpts(options, resolvedTarget, projectRoot) {
if (options.nobuild) ret.buildMethod = 'none'; if (options.nobuild) ret.buildMethod = 'none';
if (options.argv.versionCode) if (options.argv.versionCode) { ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode); }
ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode);
if (options.argv.minSdkVersion) if (options.argv.minSdkVersion) { ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion); }
ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion);
if (options.argv.gradleArg) { if (options.argv.gradleArg) {
ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg); ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
@ -76,12 +74,10 @@ function parseOpts(options, resolvedTarget, projectRoot) {
var packageArgs = {}; var packageArgs = {};
if (options.argv.keystore) if (options.argv.keystore) { packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore)); }
packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore));
['alias','storePassword','password','keystoreType'].forEach(function (flagName) { ['alias', 'storePassword', 'password', 'keystoreType'].forEach(function (flagName) {
if (options.argv[flagName]) if (options.argv[flagName]) { packageArgs[flagName] = options.argv[flagName]; }
packageArgs[flagName] = options.argv[flagName];
}); });
var buildConfig = options.buildConfig; var buildConfig = options.buildConfig;
@ -92,20 +88,20 @@ function parseOpts(options, resolvedTarget, projectRoot) {
if (!fs.existsSync(buildConfig)) { if (!fs.existsSync(buildConfig)) {
throw new Error('Specified build config file does not exist: ' + buildConfig); throw new Error('Specified build config file does not exist: ' + buildConfig);
} }
events.emit('log', 'Reading build config file: '+ path.resolve(buildConfig)); events.emit('log', 'Reading build config file: ' + path.resolve(buildConfig));
var buildjson = fs.readFileSync(buildConfig, 'utf8'); var buildjson = fs.readFileSync(buildConfig, 'utf8');
var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
if (config.android && config.android[ret.buildType]) { if (config.android && config.android[ret.buildType]) {
var androidInfo = config.android[ret.buildType]; var androidInfo = config.android[ret.buildType];
if(androidInfo.keystore && !packageArgs.keystore) { if (androidInfo.keystore && !packageArgs.keystore) {
if(androidInfo.keystore.substr(0,1) === '~') { if (androidInfo.keystore.substr(0, 1) === '~') {
androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1); androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
} }
packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore); packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore); events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
} }
['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){ ['alias', 'storePassword', 'password', 'keystoreType'].forEach(function (key) {
packageArgs[key] = packageArgs[key] || androidInfo[key]; packageArgs[key] = packageArgs[key] || androidInfo[key];
}); });
} }
@ -116,8 +112,8 @@ function parseOpts(options, resolvedTarget, projectRoot) {
packageArgs.password, packageArgs.keystoreType); packageArgs.password, packageArgs.keystoreType);
} }
if(!ret.packageInfo) { if (!ret.packageInfo) {
if(Object.keys(packageArgs).length > 0) { if (Object.keys(packageArgs).length > 0) {
events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.'); events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
} }
} }
@ -129,11 +125,10 @@ function parseOpts(options, resolvedTarget, projectRoot) {
* Builds the project with the specifed options * Builds the project with the specifed options
* Returns a promise. * Returns a promise.
*/ */
module.exports.runClean = function(options) { module.exports.runClean = function (options) {
var opts = parseOpts(options, null, this.root); var opts = parseOpts(options, null, this.root);
var builder = builders.getBuilder(opts.buildMethod); var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts) return builder.prepEnv(opts).then(function () {
.then(function() {
return builder.clean(opts); return builder.clean(opts);
}); });
}; };
@ -150,18 +145,16 @@ module.exports.runClean = function(options) {
* @return {Promise<Object>} Promise, resolved with built packages * @return {Promise<Object>} Promise, resolved with built packages
* information. * information.
*/ */
module.exports.run = function(options, optResolvedTarget) { module.exports.run = function (options, optResolvedTarget) {
var opts = parseOpts(options, optResolvedTarget, this.root); var opts = parseOpts(options, optResolvedTarget, this.root);
console.log(opts.buildMethod); console.log(opts.buildMethod);
var builder = builders.getBuilder(opts.buildMethod); var builder = builders.getBuilder(opts.buildMethod);
return builder.prepEnv(opts) return builder.prepEnv(opts).then(function () {
.then(function() {
if (opts.prepEnv) { if (opts.prepEnv) {
events.emit('verbose', 'Build file successfully prepared.'); events.emit('verbose', 'Build file successfully prepared.');
return; return;
} }
return builder.build(opts) return builder.build(opts).then(function () {
.then(function() {
var apkPaths = builder.findOutputApks(opts.buildType, opts.arch); var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t')); events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
return { return {
@ -177,38 +170,31 @@ module.exports.run = function(options, optResolvedTarget) {
* Detects the architecture of a device/emulator * Detects the architecture of a device/emulator
* Returns "arm" or "x86". * Returns "arm" or "x86".
*/ */
module.exports.detectArchitecture = function(target) { module.exports.detectArchitecture = function (target) {
function helper() { function helper () {
return Adb.shell(target, 'cat /proc/cpuinfo') return Adb.shell(target, 'cat /proc/cpuinfo').then(function (output) {
.then(function(output) {
return /intel/i.exec(output) ? 'x86' : 'arm'; return /intel/i.exec(output) ? 'x86' : 'arm';
}); });
} }
// It sometimes happens (at least on OS X), that this command will hang forever. // 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. // To fix it, either unplug & replug device, or restart adb server.
return helper() return helper().timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.')).then(null, function (err) {
.timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
.then(null, function(err) {
if (/timed out/.exec('' + err)) { if (/timed out/.exec('' + err)) {
// adb kill-server doesn't seem to do the trick. // adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually // Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines. // 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.'); events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
return spawn('killall', ['adb']) return spawn('killall', ['adb']).then(function () {
.then(function() { return helper().then(null, function () {
return helper()
.then(null, function() {
// The double kill is sadly often necessary, at least on mac. // 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.'); events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
return spawn('killall', ['adb']) return spawn('killall', ['adb']).then(function () {
.then(function() { return helper().then(null, 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.')); return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
}); });
}); });
}); });
}, function() { }, function () {
// For non-killall OS's. // For non-killall OS's.
return Q.reject(err); return Q.reject(err);
}); });
@ -217,10 +203,10 @@ module.exports.detectArchitecture = function(target) {
}); });
}; };
module.exports.findBestApkForArchitecture = function(buildResults, arch) { module.exports.findBestApkForArchitecture = function (buildResults, arch) {
var paths = buildResults.apkPaths.filter(function(p) { var paths = buildResults.apkPaths.filter(function (p) {
var apkName = path.basename(p); var apkName = path.basename(p);
if (buildResults.buildType == 'debug') { if (buildResults.buildType === 'debug') {
return /-debug/.exec(apkName); return /-debug/.exec(apkName);
} }
return !/-debug/.exec(apkName); return !/-debug/.exec(apkName);
@ -240,7 +226,7 @@ module.exports.findBestApkForArchitecture = function(buildResults, arch) {
throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType); throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
}; };
function PackageInfo(keystore, alias, storePassword, password, keystoreType) { function PackageInfo (keystore, alias, storePassword, password, keystoreType) {
this.keystore = { this.keystore = {
'name': 'key.store', 'name': 'key.store',
'value': keystore 'value': keystore
@ -270,10 +256,10 @@ function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
} }
PackageInfo.prototype = { PackageInfo.prototype = {
toProperties: function() { toProperties: function () {
var self = this; var self = this;
var result = ''; var result = '';
Object.keys(self).forEach(function(key) { Object.keys(self).forEach(function (key) {
result += self[key].name; result += self[key].name;
result += '='; result += '=';
result += self[key].value.replace(/\\/g, '\\\\'); result += self[key].value.replace(/\\/g, '\\\\');
@ -283,7 +269,7 @@ PackageInfo.prototype = {
} }
}; };
module.exports.help = function() { module.exports.help = function () {
console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]'); console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
console.log('Flags:'); console.log('Flags:');
console.log(' \'--debug\': will build project in debug mode (default)'); console.log(' \'--debug\': will build project in debug mode (default)');

View File

@ -16,6 +16,8 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* eslint no-self-assign: 0 */
/* eslint no-unused-vars: 0 */
var Q = require('q'); var Q = require('q');
var fs = require('fs'); var fs = require('fs');
@ -35,29 +37,27 @@ GenericBuilder.prototype.prepEnv = function() {
return Q(); return Q();
}; };
GenericBuilder.prototype.build = function() { GenericBuilder.prototype.build = function () {
events.emit('log', 'Skipping build...'); events.emit('log', 'Skipping build...');
return Q(null); return Q(null);
}; };
GenericBuilder.prototype.clean = function() { GenericBuilder.prototype.clean = function () {
return Q(); return Q();
}; };
GenericBuilder.prototype.findOutputApks = function(build_type, arch) { GenericBuilder.prototype.findOutputApks = function (build_type, arch) {
var self = this; var self = this;
return Object.keys(this.binDirs) return Object.keys(this.binDirs) .reduce(function (result, builderName) {
.reduce(function (result, builderName) {
console.log('builderName:'+ builderName); console.log('builderName:'+ builderName);
var binDir = self.binDirs[builderName]; var binDir = self.binDirs[builderName];
return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch)); return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch));
}, []) }, []).sort(apkSorter);
.sort(apkSorter);
}; };
module.exports = GenericBuilder; module.exports = GenericBuilder;
function apkSorter(fileA, fileB) { function apkSorter (fileA, fileB) {
// De-prioritize unsigned builds // De-prioritize unsigned builds
var unsignedRE = /-unsigned/; var unsignedRE = /-unsigned/;
if (unsignedRE.exec(fileA)) { if (unsignedRE.exec(fileA)) {
@ -70,12 +70,11 @@ function apkSorter(fileA, fileB) {
return timeDiff === 0 ? fileA.length - fileB.length : timeDiff; return timeDiff === 0 ? fileA.length - fileB.length : timeDiff;
} }
function findOutputApksHelper(dir, build_type, arch) { function findOutputApksHelper (dir, build_type, arch) {
var shellSilent = shell.config.silent; var shellSilent = shell.config.silent;
shell.config.silent = true; shell.config.silent = true;
var ret = shell.ls(path.join(dir, '*.apk')) var ret = shell.ls(path.join(dir, '*.apk')).filter(function (candidate) {
.filter(function(candidate) {
var apkName = path.basename(candidate); var apkName = path.basename(candidate);
// Need to choose between release and debug .apk. // Need to choose between release and debug .apk.
if (build_type === 'debug') { if (build_type === 'debug') {
@ -85,8 +84,7 @@ function findOutputApksHelper(dir, build_type, arch) {
return /-release/.exec(apkName) && !/-unaligned/.exec(apkName); return /-release/.exec(apkName) && !/-unaligned/.exec(apkName);
} }
return true; return true;
}) }).sort(apkSorter);
.sort(apkSorter);
shellSilent = shellSilent; shellSilent = shellSilent;
@ -96,15 +94,15 @@ function findOutputApksHelper(dir, build_type, arch) {
// Assume arch-specific build if newest apk has -x86 or -arm. // Assume arch-specific build if newest apk has -x86 or -arm.
var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0])); var archSpecific = !!/-x86|-arm/.exec(path.basename(ret[0]));
// And show only arch-specific ones (or non-arch-specific) // And show only arch-specific ones (or non-arch-specific)
ret = ret.filter(function(p) { ret = ret.filter(function (p) {
/*jshint -W018 */ /* jshint -W018 */
return !!/-x86|-arm/.exec(path.basename(p)) == archSpecific; return !!/-x86|-arm/.exec(path.basename(p)) === archSpecific;
/*jshint +W018 */ /* jshint +W018 */
}); });
if (archSpecific && ret.length > 1 && arch) { if (archSpecific && ret.length > 1 && arch) {
ret = ret.filter(function(p) { ret = ret.filter(function (p) {
return path.basename(p).indexOf('-' + arch) != -1; return path.basename(p).indexOf('-' + arch) !== -1;
}); });
} }

View File

@ -42,10 +42,10 @@ function GradleBuilder (projectRoot) {
util.inherits(GradleBuilder, GenericBuilder); util.inherits(GradleBuilder, GenericBuilder);
GradleBuilder.prototype.getArgs = function(cmd, opts) { GradleBuilder.prototype.getArgs = function (cmd, opts) {
if (cmd == 'release') { if (cmd === 'release') {
cmd = 'cdvBuildRelease'; cmd = 'cdvBuildRelease';
} else if (cmd == 'debug') { } else if (cmd === 'debug') {
cmd = 'cdvBuildDebug'; cmd = 'cdvBuildDebug';
} }
var args = [cmd, '-b', path.join(this.root, 'build.gradle')]; var args = [cmd, '-b', path.join(this.root, 'build.gradle')];
@ -69,13 +69,13 @@ GradleBuilder.prototype.getArgs = function(cmd, opts) {
* This returns a promise * This returns a promise
*/ */
GradleBuilder.prototype.runGradleWrapper = function(gradle_cmd) { GradleBuilder.prototype.runGradleWrapper = function (gradle_cmd) {
var gradlePath = path.join(this.root, 'gradlew'); var gradlePath = path.join(this.root, 'gradlew');
var wrapperGradle = path.join(this.root, 'wrapper.gradle'); var wrapperGradle = path.join(this.root, 'wrapper.gradle');
if(fs.existsSync(gradlePath)) { if (fs.existsSync(gradlePath)) {
//Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows // Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows
} else { } else {
return superspawn.spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], {stdio: 'inherit'}); return superspawn.spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], {stdio: 'inherit'});
} }
}; };
@ -116,7 +116,7 @@ GradleBuilder.prototype.extractRealProjectNameFromManifest = function () {
// Makes the project buildable, minus the gradle wrapper. // Makes the project buildable, minus the gradle wrapper.
GradleBuilder.prototype.prepBuildFiles = function() { GradleBuilder.prototype.prepBuildFiles = function () {
// Update the version of build.gradle in each dependent library. // Update the version of build.gradle in each dependent library.
var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle'); var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle');
var propertiesObj = this.readProjectProperties(); var propertiesObj = this.readProjectProperties();
@ -125,33 +125,32 @@ GradleBuilder.prototype.prepBuildFiles = function() {
// Check and copy the gradle file into the subproject. // Check and copy the gradle file into the subproject.
// Called by the loop below this function def. // Called by the loop below this function def.
var checkAndCopy = function(subProject, root) { var checkAndCopy = function(subProject, root) {
var subProjectGradle = path.join(root, subProject, 'build.gradle'); var subProjectGradle = path.join(root, subProject, 'build.gradle');
// This is the future-proof way of checking if a file exists // This is the future-proof way of checking if a file exists
// This must be synchronous to satisfy a Travis test // This must be synchronous to satisfy a Travis test
try { try {
fs.accessSync(subProjectGradle, fs.F_OK); fs.accessSync(subProjectGradle, fs.F_OK);
} catch (e) { } catch (e) {
shell.cp('-f', pluginBuildGradle, subProjectGradle); shell.cp('-f', pluginBuildGradle, subProjectGradle);
} }
}; };
// Some dependencies on Android don't use gradle, or don't have default // Some dependencies on Android don't use gradle, or don't have default
// gradle files. This copies a dummy gradle file into them // gradle files. This copies a dummy gradle file into them
for (var i = 0; i < subProjects.length; ++i) { for (var i = 0; i < subProjects.length; ++i) {
if (subProjects[i] !== 'CordovaLib' && subProjects[i] !== 'app') { if (subProjects[i] !== 'CordovaLib' && subProjects[i] !== 'app') {
checkAndCopy(subProjects[i], this.root); checkAndCopy(subProjects[i], this.root);
} }
} }
var name = this.extractRealProjectNameFromManifest(); var name = this.extractRealProjectNameFromManifest();
//Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149 // Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
var settingsGradlePaths = subProjects.map(function(p){ var settingsGradlePaths = subProjects.map(function (p) {
var realDir=p.replace(/[/\\]/g, ':'); var realDir = p.replace(/[/\\]/g, ':');
var libName=realDir.replace(name+'-',''); var libName = realDir.replace(name + '-', '');
var str='include ":'+libName+'"\n'; var str = 'include ":' + libName + '"\n';
if(realDir.indexOf(name+'-')!==-1) if (realDir.indexOf(name + '-') !== -1) { str += 'project(":' + libName + '").projectDir = new File("' + p + '")\n'; }
str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
return str; return str;
}); });
@ -169,19 +168,18 @@ GradleBuilder.prototype.prepBuildFiles = function() {
// dependency. Because we need to make sure that Cordova is compiled only once for // dependency. Because we need to make sure that Cordova is compiled only once for
// dexing, we make sure to exclude CordovaLib from these modules // dexing, we make sure to exclude CordovaLib from these modules
var insertExclude = function(p) { var insertExclude = function(p) {
var gradlePath = path.join(root, p, 'build.gradle'); var gradlePath = path.join(root, p, 'build.gradle');
var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8'); var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8');
if(projectGradleFile.indexOf('CordovaLib') != -1) { if(projectGradleFile.indexOf('CordovaLib') != -1) {
depsList += '{\n exclude module:("CordovaLib")\n }\n'; depsList += '{\n exclude module:("CordovaLib")\n }\n';
} } else {
else { depsList += '\n';
depsList +='\n'; }
}
}; };
subProjects.forEach(function(p) { subProjects.forEach(function (p) {
console.log('Subproject Path: ' + p); console.log('Subproject Path: ' + p);
var libName=p.replace(/[/\\]/g, ':').replace(name+'-',''); var libName = p.replace(/[/\\]/g, ':').replace(name + '-', '');
depsList += ' debugCompile(project(path: "' + libName + '", configuration: "debug"))'; depsList += ' debugCompile(project(path: "' + libName + '", configuration: "debug"))';
insertExclude(p); insertExclude(p);
depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))'; depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))';
@ -194,8 +192,7 @@ GradleBuilder.prototype.prepBuildFiles = function() {
[/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'], [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
[/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+'] [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
]; ];
propertiesObj.systemLibs.forEach(function (p) {
propertiesObj.systemLibs.forEach(function(p) {
var mavenRef; var mavenRef;
// It's already in gradle form if it has two ':'s // It's already in gradle form if it has two ':'s
if (/:.*:/.exec(p)) { if (/:.*:/.exec(p)) {
@ -219,21 +216,20 @@ GradleBuilder.prototype.prepBuildFiles = function() {
//Try not to mess with this if possible //Try not to mess with this if possible
buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2'); buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
var includeList = ''; var includeList = '';
propertiesObj.gradleIncludes.forEach(function(includePath) { propertiesObj.gradleIncludes.forEach(function (includePath) {
includeList += 'apply from: "' + includePath + '"\n'; includeList += 'apply from: "' + includePath + '"\n';
}); });
buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2'); 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); fs.writeFileSync(path.join(this.root, 'build.gradle'), buildGradle);
}; };
GradleBuilder.prototype.prepEnv = function(opts) { GradleBuilder.prototype.prepEnv = function (opts) {
var self = this; var self = this;
return check_reqs.check_gradle() return check_reqs.check_gradle().then(function (gradlePath) {
.then(function(gradlePath) {
return self.runGradleWrapper(gradlePath); return self.runGradleWrapper(gradlePath);
}).then(function() { }).then(function () {
return self.prepBuildFiles(); return self.prepBuildFiles();
}).then(function() { }).then(function () {
// We now copy the gradle out of the framework // We now copy the gradle out of the framework
// This is a dirty patch to get the build working // This is a dirty patch to get the build working
/* /*
@ -253,12 +249,12 @@ GradleBuilder.prototype.prepEnv = function(opts) {
// 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. // 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. // For some reason, using ^ and $ don't work. This does the job, though.
var distributionUrlRegex = /distributionUrl.*zip/; var distributionUrlRegex = /distributionUrl.*zip/;
/*jshint -W069 */ /* jshint -W069 */
var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-3.3-all.zip'; var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-3.3-all.zip';
/*jshint +W069 */ /* jshint +W069 */
var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties'); var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties');
shell.chmod('u+w', gradleWrapperPropertiesPath); shell.chmod('u+w', gradleWrapperPropertiesPath);
shell.sed('-i', distributionUrlRegex, 'distributionUrl='+distributionUrl, gradleWrapperPropertiesPath); shell.sed('-i', distributionUrlRegex, 'distributionUrl=' + distributionUrl, gradleWrapperPropertiesPath);
var propertiesFile = opts.buildType + SIGNING_PROPERTIES; var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
var propertiesFilePath = path.join(self.root, propertiesFile); var propertiesFilePath = path.join(self.root, propertiesFile);
@ -274,12 +270,11 @@ GradleBuilder.prototype.prepEnv = function(opts) {
* Builds the project with gradle. * Builds the project with gradle.
* Returns a promise. * Returns a promise.
*/ */
GradleBuilder.prototype.build = function(opts) { GradleBuilder.prototype.build = function (opts) {
var wrapper = path.join(this.root, 'gradlew'); var wrapper = path.join(this.root, 'gradlew');
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts); var args = this.getArgs(opts.buildType === 'debug' ? 'debug' : 'release', opts);
return superspawn.spawn(wrapper, args, {stdio: 'pipe'}) return superspawn.spawn(wrapper, args, {stdio: 'pipe'}).progress(function (stdio) {
.progress(function (stdio){
if (stdio.stderr) { if (stdio.stderr) {
/* /*
* Workaround for the issue with Java printing some unwanted information to * Workaround for the issue with Java printing some unwanted information to
@ -298,7 +293,7 @@ GradleBuilder.prototype.build = function(opts) {
} }
}).catch(function (error) { }).catch(function (error) {
if (error.toString().indexOf('failed to find target with hash string') >= 0) { if (error.toString().indexOf('failed to find target with hash string') >= 0) {
return check_reqs.check_android_target(error).then(function() { return check_reqs.check_android_target(error).then(function () {
// If due to some odd reason - check_android_target succeeds // If due to some odd reason - check_android_target succeeds
// we should still fail here. // we should still fail here.
return Q.reject(error); return Q.reject(error);
@ -308,19 +303,18 @@ GradleBuilder.prototype.build = function(opts) {
}); });
}; };
GradleBuilder.prototype.clean = function(opts) { GradleBuilder.prototype.clean = function (opts) {
var builder = this; var builder = this;
var wrapper = path.join(this.root, 'gradlew'); var wrapper = path.join(this.root, 'gradlew');
var args = builder.getArgs('clean', opts); var args = builder.getArgs('clean', opts);
return Q().then(function() { return Q().then(function () {
return superspawn.spawn(wrapper, args, {stdio: 'inherit'}); return superspawn.spawn(wrapper, args, {stdio: 'inherit'});
}) }).then(function () {
.then(function () {
shell.rm('-rf', path.join(builder.root, 'out')); shell.rm('-rf', path.join(builder.root, 'out'));
['debug', 'release'].forEach(function(config) { ['debug', 'release'].forEach(function (config) {
var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES); var propertiesFilePath = path.join(builder.root, config + SIGNING_PROPERTIES);
if(isAutoGenerated(propertiesFilePath)){ if (isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath); shell.rm('-f', propertiesFilePath);
} }
}); });
@ -329,6 +323,6 @@ GradleBuilder.prototype.clean = function(opts) {
module.exports = GradleBuilder; module.exports = GradleBuilder;
function isAutoGenerated(file) { function isAutoGenerated (file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0; return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
} }

View File

@ -35,8 +35,7 @@ var knownBuilders = {
* @return {Builder} A builder instance for specified build type. * @return {Builder} A builder instance for specified build type.
*/ */
module.exports.getBuilder = function (builderType, projectRoot) { module.exports.getBuilder = function (builderType, projectRoot) {
if (!knownBuilders[builderType]) if (!knownBuilders[builderType]) { throw new CordovaError('Builder ' + builderType + ' is not supported.'); }
throw new CordovaError('Builder ' + builderType + ' is not supported.');
try { try {
var Builder = require('./' + knownBuilders[builderType]); var Builder = require('./' + knownBuilders[builderType]);

View File

@ -21,19 +21,19 @@
/* jshint sub:true */ /* jshint sub:true */
var shelljs = require('shelljs'), var shelljs = require('shelljs');
child_process = require('child_process'), var child_process = require('child_process');
Q = require('q'), var Q = require('q');
path = require('path'), var path = require('path');
fs = require('fs'), var fs = require('fs');
os = require('os'), var os = require('os');
REPO_ROOT = path.join(__dirname, '..', '..', '..', '..'), var REPO_ROOT = path.join(__dirname, '..', '..', '..', '..');
PROJECT_ROOT = path.join(__dirname, '..', '..'); var PROJECT_ROOT = path.join(__dirname, '..', '..');
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var superspawn = require('cordova-common').superspawn; var superspawn = require('cordova-common').superspawn;
var android_sdk = require('./android_sdk'); var android_sdk = require('./android_sdk');
function forgivingWhichSync(cmd) { function forgivingWhichSync (cmd) {
try { try {
return fs.realpathSync(shelljs.which(cmd)); return fs.realpathSync(shelljs.which(cmd));
} catch (e) { } catch (e) {
@ -41,9 +41,9 @@ function forgivingWhichSync(cmd) {
} }
} }
function tryCommand(cmd, errMsg, catchStderr) { function tryCommand (cmd, errMsg, catchStderr) {
var d = Q.defer(); var d = Q.defer();
child_process.exec(cmd, function(err, stdout, stderr) { child_process.exec(cmd, function (err, stdout, stderr) {
if (err) d.reject(new CordovaError(errMsg)); if (err) d.reject(new CordovaError(errMsg));
// Sometimes it is necessary to return an stderr instead of stdout in case of success, since // Sometimes it is necessary to return an stderr instead of stdout in case of success, since
// some commands prints theirs output to stderr instead of stdout. 'javac' is the example // some commands prints theirs output to stderr instead of stdout. 'javac' is the example
@ -52,18 +52,18 @@ function tryCommand(cmd, errMsg, catchStderr) {
return d.promise; return d.promise;
} }
module.exports.isWindows = function() { module.exports.isWindows = function () {
return (os.platform() == 'win32'); return (os.platform() === 'win32');
}; };
module.exports.isDarwin = function() { module.exports.isDarwin = function () {
return (os.platform() == 'darwin'); return (os.platform() === 'darwin');
}; };
// Get valid target from framework/project.properties if run from this repo // Get valid target from framework/project.properties if run from this repo
// Otherwise get target from project.properties file within a generated cordova-android project // Otherwise get target from project.properties file within a generated cordova-android project
module.exports.get_target = function() { module.exports.get_target = function () {
function extractFromFile(filePath) { function extractFromFile (filePath) {
var target = shelljs.grep(/\btarget=/, filePath); var target = shelljs.grep(/\btarget=/, filePath);
if (!target) { if (!target) {
throw new Error('Could not find android target within: ' + filePath); throw new Error('Could not find android target within: ' + filePath);
@ -83,17 +83,18 @@ module.exports.get_target = function() {
}; };
// Returns a promise. Called only by build and clean commands. // Returns a promise. Called only by build and clean commands.
module.exports.check_ant = function() { module.exports.check_ant = function () {
return superspawn.spawn('ant', ['-version']) return superspawn.spawn('ant', ['-version']).then(function (output) {
.then(function(output) {
// Parse Ant version from command output // Parse Ant version from command output
return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
}).catch(function(err) { }).catch(function (err) {
throw new CordovaError('Failed to run `ant -version`. Make sure you have `ant` on your $PATH.'); if (err) {
throw new CordovaError('Failed to run `ant -version`. Make sure you have `ant` on your $PATH.');
}
}); });
}; };
module.exports.get_gradle_wrapper = function() { module.exports.get_gradle_wrapper = function () {
var androidStudioPath; var androidStudioPath;
var i = 0; var i = 0;
var foundStudio = false; var foundStudio = false;
@ -102,58 +103,70 @@ module.exports.get_gradle_wrapper = function() {
program_dir = fs.readdirSync('/Applications'); program_dir = fs.readdirSync('/Applications');
while (i < program_dir.length && !foundStudio) { while (i < program_dir.length && !foundStudio) {
if (program_dir[i].startsWith('Android Studio')) { if (program_dir[i].startsWith('Android Studio')) {
//TODO: Check for a specific Android Studio version, make sure it's not Canary // TODO: Check for a specific Android Studio version, make sure it's not Canary
androidStudioPath = path.join('/Applications', program_dir[i], 'Contents', 'gradle'); androidStudioPath = path.join('/Applications', program_dir[i], 'Contents', 'gradle');
foundStudio = true; foundStudio = true;
} else { ++i; } } else { ++i; }
} }
} else if (module.exports.isWindows()) { } else if (module.exports.isWindows()) {
var androidPath = path.join(process.env['ProgramFiles'], 'Android') + '/';
if (fs.existsSync(androidPath)) { var result = child_process.spawnSync(path.join(__dirname, 'getASPath.bat'));
program_dir = fs.readdirSync(androidPath); // console.log('result.stdout =' + result.stdout.toString());
while (i < program_dir.length && !foundStudio) { // console.log('result.stderr =' + result.stderr.toString());
if (program_dir[i].startsWith('Android Studio')) {
foundStudio = true; if (result.stderr.toString().length > 0) {
androidStudioPath = path.join(process.env['ProgramFiles'], 'Android', program_dir[i], 'gradle'); var androidPath = path.join(process.env['ProgramFiles'], 'Android') + '/';
} else { ++i; } if (fs.existsSync(androidPath)) {
program_dir = fs.readdirSync(androidPath);
while (i < program_dir.length && !foundStudio) {
if (program_dir[i].startsWith('Android Studio')) {
foundStudio = true;
androidStudioPath = path.join(process.env['ProgramFiles'], 'Android', program_dir[i], 'gradle');
} else { ++i; }
}
} }
} else {
// console.log('got android studio path from registry');
// remove the (os independent) new line char at the end of stdout
// add gradle to match the above.
androidStudioPath = path.join(result.stdout.toString().split('\r\n')[0], 'gradle');
} }
} }
if (androidStudioPath !== null && fs.existsSync(androidStudioPath)) { if (androidStudioPath !== null && fs.existsSync(androidStudioPath)) {
var dirs = fs.readdirSync(androidStudioPath); var dirs = fs.readdirSync(androidStudioPath);
if(dirs[0].split('-')[0] == 'gradle') { if (dirs[0].split('-')[0] === 'gradle') {
return path.join(androidStudioPath, dirs[0], 'bin', 'gradle'); return path.join(androidStudioPath, dirs[0], 'bin', 'gradle');
} }
} else { } else {
//OK, let's try to check for Gradle! // OK, let's try to check for Gradle!
return forgivingWhichSync('gradle'); return forgivingWhichSync('gradle');
} }
}; };
// Returns a promise. Called only by build and clean commands. // Returns a promise. Called only by build and clean commands.
module.exports.check_gradle = function() { module.exports.check_gradle = function () {
var sdkDir = process.env['ANDROID_HOME']; var sdkDir = process.env['ANDROID_HOME'];
var d = Q.defer(); var d = Q.defer();
if (!sdkDir) if (!sdkDir) {
return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' + return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.')); 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'));
}
var gradlePath = module.exports.get_gradle_wrapper(); var gradlePath = module.exports.get_gradle_wrapper();
if (gradlePath.length !== 0) if (gradlePath.length !== 0) { d.resolve(gradlePath); } else {
d.resolve(gradlePath);
else
d.reject(new CordovaError('Could not find an installed version of Gradle either in Android Studio,\n' + d.reject(new CordovaError('Could not find an installed version of Gradle either in Android Studio,\n' +
'or on your system to install the gradle wrapper. Please include gradle \n' + 'or on your system to install the gradle wrapper. Please include gradle \n' +
'in your path, or install Android Studio')); 'in your path, or install Android Studio'));
}
return d.promise; return d.promise;
}; };
// Returns a promise. // Returns a promise.
module.exports.check_java = function() { module.exports.check_java = function () {
var javacPath = forgivingWhichSync('javac'); var javacPath = forgivingWhichSync('javac');
var hasJavaHome = !!process.env['JAVA_HOME']; var hasJavaHome = !!process.env['JAVA_HOME'];
return Q().then(function() { return Q().then(function () {
if (hasJavaHome) { if (hasJavaHome) {
// Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh). // Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
if (!javacPath) { if (!javacPath) {
@ -165,11 +178,12 @@ module.exports.check_java = function() {
var find_java = '/usr/libexec/java_home'; var find_java = '/usr/libexec/java_home';
var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.'; var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.';
if (fs.existsSync(find_java)) { if (fs.existsSync(find_java)) {
return superspawn.spawn(find_java) return superspawn.spawn(find_java).then(function (stdout) {
.then(function(stdout) {
process.env['JAVA_HOME'] = stdout.trim(); process.env['JAVA_HOME'] = stdout.trim();
}).catch(function(err) { }).catch(function (err) {
throw new CordovaError(default_java_error_msg); if (err) {
throw new CordovaError(default_java_error_msg);
}
}); });
} else { } else {
// See if we can derive it from javac's location. // See if we can derive it from javac's location.
@ -200,7 +214,7 @@ module.exports.check_java = function() {
} }
} }
} }
}).then(function() { }).then(function () {
var msg = var msg =
'Failed to run "javac -version", make sure that you have a JDK installed.\n' + 'Failed to run "javac -version", make sure that you have a JDK installed.\n' +
'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n'; 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
@ -209,9 +223,8 @@ module.exports.check_java = function() {
} }
// We use tryCommand with catchStderr = true, because // We use tryCommand with catchStderr = true, because
// javac writes version info to stderr instead of stdout // javac writes version info to stderr instead of stdout
return tryCommand('javac -version', msg, true) return tryCommand('javac -version', msg, true).then(function (output) {
.then(function (output) { // Let's check for at least Java 8, and keep it future proof so we can support Java 10
//Let's check for at least Java 8, and keep it future proof so we can support Java 10
var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output); var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output);
return match && match[1]; return match && match[1];
}); });
@ -219,13 +232,13 @@ module.exports.check_java = function() {
}; };
// Returns a promise. // Returns a promise.
module.exports.check_android = function() { module.exports.check_android = function () {
return Q().then(function() { return Q().then(function () {
var androidCmdPath = forgivingWhichSync('android'); var androidCmdPath = forgivingWhichSync('android');
var adbInPath = forgivingWhichSync('adb'); var adbInPath = forgivingWhichSync('adb');
var avdmanagerInPath = forgivingWhichSync('avdmanager'); var avdmanagerInPath = forgivingWhichSync('avdmanager');
var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
function maybeSetAndroidHome(value) { function maybeSetAndroidHome (value) {
if (!hasAndroidHome && fs.existsSync(value)) { if (!hasAndroidHome && fs.existsSync(value)) {
hasAndroidHome = true; hasAndroidHome = true;
process.env['ANDROID_HOME'] = value; process.env['ANDROID_HOME'] = value;
@ -265,7 +278,7 @@ module.exports.check_android = function() {
if (androidCmdPath) { if (androidCmdPath) {
parentDir = path.dirname(androidCmdPath); parentDir = path.dirname(androidCmdPath);
grandParentDir = path.dirname(parentDir); grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'tools' || fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) { if (path.basename(parentDir) === 'tools' || fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
maybeSetAndroidHome(grandParentDir); maybeSetAndroidHome(grandParentDir);
} else { } else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
@ -276,7 +289,7 @@ module.exports.check_android = function() {
if (adbInPath) { if (adbInPath) {
parentDir = path.dirname(adbInPath); parentDir = path.dirname(adbInPath);
grandParentDir = path.dirname(parentDir); grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'platform-tools') { if (path.basename(parentDir) === 'platform-tools') {
maybeSetAndroidHome(grandParentDir); maybeSetAndroidHome(grandParentDir);
} else { } else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
@ -287,7 +300,7 @@ module.exports.check_android = function() {
if (avdmanagerInPath) { if (avdmanagerInPath) {
parentDir = path.dirname(avdmanagerInPath); parentDir = path.dirname(avdmanagerInPath);
grandParentDir = path.dirname(parentDir); grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'bin' && path.basename(grandParentDir) == 'tools') { if (path.basename(parentDir) === 'bin' && path.basename(grandParentDir) === 'tools') {
maybeSetAndroidHome(path.dirname(grandParentDir)); maybeSetAndroidHome(path.dirname(grandParentDir));
} else { } else {
throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
@ -330,20 +343,19 @@ module.exports.getAbsoluteAndroidCmd = function () {
return cmd.replace(/(\s)/g, '\\$1'); return cmd.replace(/(\s)/g, '\\$1');
}; };
module.exports.check_android_target = function(originalError) { module.exports.check_android_target = function (originalError) {
// valid_target can look like: // valid_target can look like:
// android-19 // android-19
// android-L // android-L
// Google Inc.:Google APIs:20 // Google Inc.:Google APIs:20
// Google Inc.:Glass Development Kit Preview:20 // Google Inc.:Glass Development Kit Preview:20
var desired_api_level = module.exports.get_target(); var desired_api_level = module.exports.get_target();
return android_sdk.list_targets() return android_sdk.list_targets().then(function (targets) {
.then(function(targets) {
if (targets.indexOf(desired_api_level) >= 0) { if (targets.indexOf(desired_api_level) >= 0) {
return targets; return targets;
} }
var androidCmd = module.exports.getAbsoluteAndroidCmd(); var androidCmd = module.exports.getAbsoluteAndroidCmd();
var msg = 'Please install Android target / API level: "' + desired_api_level + '".\n\n' + var msg = 'Please install Android target / API level: "' + desired_api_level + '".\n\n' +
'Hint: Open the SDK manager by running: ' + androidCmd + '\n' + 'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
'You will require:\n' + 'You will require:\n' +
'1. "SDK Platform" for API level ' + desired_api_level + '\n' + '1. "SDK Platform" for API level ' + desired_api_level + '\n' +
@ -357,23 +369,21 @@ module.exports.check_android_target = function(originalError) {
}; };
// Returns a promise. // Returns a promise.
module.exports.run = function() { module.exports.run = function () {
return Q.all([this.check_java(), this.check_android()]) return Q.all([this.check_java(), this.check_android()]).then(function (values) {
.then(function(values) { console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']); console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
if (!values[0]) { if (!values[0]) {
throw new CordovaError('Requirements check failed for JDK 1.8 or greater'); throw new CordovaError('Requirements check failed for JDK 1.8 or greater');
} }
if (!values[1]) { if (!values[1]) {
throw new CordovaError('Requirements check failed for Android SDK'); throw new CordovaError('Requirements check failed for Android SDK');
} }
}); });
}; };
/** /**
* Object thar represents one of requirements for current platform. * Object thar represents one of requirements for current platform.
* @param {String} id The unique identifier for this requirements. * @param {String} id The unique identifier for this requirements.
@ -387,7 +397,7 @@ var Requirement = function (id, name, version, installed) {
this.name = name; this.name = name;
this.installed = installed || false; this.installed = installed || false;
this.metadata = { this.metadata = {
version: version, version: version
}; };
}; };
@ -397,7 +407,7 @@ var Requirement = function (id, name, version, installed) {
* *
* @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled. * @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled.
*/ */
module.exports.check_all = function() { module.exports.check_all = function () {
var requirements = [ var requirements = [
new Requirement('java', 'Java JDK'), new Requirement('java', 'Java JDK'),
@ -417,15 +427,13 @@ module.exports.check_all = function() {
return checkFns.reduce(function (promise, checkFn, idx) { return checkFns.reduce(function (promise, checkFn, idx) {
// Update each requirement with results // Update each requirement with results
var requirement = requirements[idx]; var requirement = requirements[idx];
return promise.then(checkFn) return promise.then(checkFn).then(function (version) {
.then(function (version) {
requirement.installed = true; requirement.installed = true;
requirement.metadata.version = version; requirement.metadata.version = version;
}, function (err) { }, function (err) {
requirement.metadata.reason = err instanceof Error ? err.message : err; requirement.metadata.reason = err instanceof Error ? err.message : err;
}); });
}, Q()) }, Q()).then(function () {
.then(function () {
// When chain is completed, return requirements array to upstream API // When chain is completed, return requirements array to upstream API
return requirements; return requirements;
}); });

View File

@ -19,8 +19,8 @@
under the License. under the License.
*/ */
var Q = require('q'), var Q = require('q');
build = require('./build'); var build = require('./build');
var path = require('path'); var path = require('path');
var Adb = require('./Adb'); var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
@ -32,18 +32,16 @@ var events = require('cordova-common').events;
* Returns a promise for the list of the device ID's found * Returns a promise for the list of the device ID's found
* @param lookHarder When true, try restarting adb if no devices are found. * @param lookHarder When true, try restarting adb if no devices are found.
*/ */
module.exports.list = function(lookHarder) { module.exports.list = function (lookHarder) {
return Adb.devices() return Adb.devices().then(function (list) {
.then(function(list) {
if (list.length === 0 && lookHarder) { if (list.length === 0 && lookHarder) {
// adb kill-server doesn't seem to do the trick. // adb kill-server doesn't seem to do the trick.
// Could probably find a x-platform version of killall, but I'm not actually // Could probably find a x-platform version of killall, but I'm not actually
// sure that this scenario even happens on non-OSX machines. // sure that this scenario even happens on non-OSX machines.
return spawn('killall', ['adb']) return spawn('killall', ['adb']).then(function () {
.then(function() {
events.emit('verbose', 'Restarting adb to see if more devices are detected.'); events.emit('verbose', 'Restarting adb to see if more devices are detected.');
return Adb.devices(); return Adb.devices();
}, function() { }, function () {
// For non-killall OS's. // For non-killall OS's.
return list; return list;
}); });
@ -52,9 +50,8 @@ module.exports.list = function(lookHarder) {
}); });
}; };
module.exports.resolveTarget = function(target) { module.exports.resolveTarget = function (target) {
return this.list(true) return this.list(true).then(function (device_list) {
.then(function(device_list) {
if (!device_list || !device_list.length) { if (!device_list || !device_list.length) {
return Q.reject(new CordovaError('Failed to deploy to device, no devices found.')); return Q.reject(new CordovaError('Failed to deploy to device, no devices found.'));
} }
@ -65,8 +62,7 @@ module.exports.resolveTarget = function(target) {
return Q.reject('ERROR: Unable to find target \'' + target + '\'.'); return Q.reject('ERROR: Unable to find target \'' + target + '\'.');
} }
return build.detectArchitecture(target) return build.detectArchitecture(target).then(function (arch) {
.then(function(arch) {
return { target: target, arch: arch, isEmulator: false }; return { target: target, arch: arch, isEmulator: false };
}); });
}); });
@ -77,13 +73,13 @@ module.exports.resolveTarget = function(target) {
* and launches it. * and launches it.
* Returns a promise. * Returns a promise.
*/ */
module.exports.install = function(target, buildResults) { module.exports.install = function (target, buildResults) {
return Q().then(function() { return Q().then(function () {
if (target && typeof target == 'object') { if (target && typeof target === 'object') {
return target; return target;
} }
return module.exports.resolveTarget(target); return module.exports.resolveTarget(target);
}).then(function(resolvedTarget) { }).then(function (resolvedTarget) {
var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch); var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch);
var manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml')); var manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml'));
var pkgName = manifest.getPackageId(); var pkgName = manifest.getPackageId();
@ -91,29 +87,25 @@ module.exports.install = function(target, buildResults) {
events.emit('log', 'Using apk: ' + apk_path); events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName); events.emit('log', 'Package name: ' + pkgName);
return Adb.install(resolvedTarget.target, apk_path, {replace: true}) return Adb.install(resolvedTarget.target, apk_path, {replace: true}).catch(function (error) {
.catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that // CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate // is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
throw error;
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' + events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
'installed app already signed with different key'); 'installed app already signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed. // or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(resolvedTarget.target, pkgName) return Adb.uninstall(resolvedTarget.target, pkgName).then(function () {
.then(function() {
return Adb.install(resolvedTarget.target, apk_path, {replace: true}); return Adb.install(resolvedTarget.target, apk_path, {replace: true});
}); });
}) }).then(function () {
.then(function() { // unlock screen
//unlock screen
return Adb.shell(resolvedTarget.target, 'input keyevent 82'); return Adb.shell(resolvedTarget.target, 'input keyevent 82');
}).then(function() { }).then(function () {
return Adb.start(resolvedTarget.target, launchName); return Adb.start(resolvedTarget.target, launchName);
}).then(function() { }).then(function () {
events.emit('log', 'LAUNCH SUCCESS'); events.emit('log', 'LAUNCH SUCCESS');
}); });
}); });

View File

@ -21,8 +21,8 @@
/* jshint sub:true */ /* jshint sub:true */
var retry = require('./retry'); var retry = require('./retry');
var build = require('./build'); var build = require('./build');
var path = require('path'); var path = require('path');
var Adb = require('./Adb'); var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
@ -33,20 +33,20 @@ var shelljs = require('shelljs');
var android_sdk = require('./android_sdk'); var android_sdk = require('./android_sdk');
var check_reqs = require('./check_reqs'); var check_reqs = require('./check_reqs');
var Q = require('q'); var Q = require('q');
var os = require('os'); var os = require('os');
var fs = require('fs'); var fs = require('fs');
var child_process = require('child_process'); var child_process = require('child_process');
// constants // constants
var ONE_SECOND = 1000; // in milliseconds var ONE_SECOND = 1000; // in milliseconds
var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds var ONE_MINUTE = 60 * ONE_SECOND; // in milliseconds
var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds var INSTALL_COMMAND_TIMEOUT = 5 * ONE_MINUTE; // in milliseconds
var NUM_INSTALL_RETRIES = 3; var NUM_INSTALL_RETRIES = 3;
var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds
var EXEC_KILL_SIGNAL = 'SIGKILL'; var EXEC_KILL_SIGNAL = 'SIGKILL';
function forgivingWhichSync(cmd) { function forgivingWhichSync (cmd) {
try { try {
return fs.realpathSync(shelljs.which(cmd)); return fs.realpathSync(shelljs.which(cmd));
} catch (e) { } catch (e) {
@ -55,8 +55,7 @@ function forgivingWhichSync(cmd) {
} }
module.exports.list_images_using_avdmanager = function () { module.exports.list_images_using_avdmanager = function () {
return superspawn.spawn('avdmanager', ['list', 'avd']) return superspawn.spawn('avdmanager', ['list', 'avd']).then(function (output) {
.then(function(output) {
var response = output.split('\n'); var response = output.split('\n');
var emulator_list = []; var emulator_list = [];
for (var i = 1; i < response.length; i++) { for (var i = 1; i < response.length; i++) {
@ -108,16 +107,15 @@ module.exports.list_images_using_avdmanager = function () {
/* To just return a list of names use this /* To just return a list of names use this
if (response[i].match(/Name:\s/)) { if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', ''); emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
}*/ } */
} }
return emulator_list; return emulator_list;
}); });
}; };
module.exports.list_images_using_android = function() { module.exports.list_images_using_android = function () {
return superspawn.spawn('android', ['list', 'avd']) return superspawn.spawn('android', ['list', 'avd']).then(function (output) {
.then(function(output) {
var response = output.split('\n'); var response = output.split('\n');
var emulator_list = []; var emulator_list = [];
for (var i = 1; i < response.length; i++) { for (var i = 1; i < response.length; i++) {
@ -152,7 +150,7 @@ module.exports.list_images_using_android = function() {
/* To just return a list of names use this /* To just return a list of names use this
if (response[i].match(/Name:\s/)) { if (response[i].match(/Name:\s/)) {
emulator_list.push(response[i].split('Name: ')[1].replace('\r', ''); emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
}*/ } */
} }
return emulator_list; return emulator_list;
@ -170,13 +168,13 @@ module.exports.list_images_using_android = function() {
skin : <skin> skin : <skin>
} }
*/ */
module.exports.list_images = function() { module.exports.list_images = function () {
if (forgivingWhichSync('avdmanager')) { if (forgivingWhichSync('avdmanager')) {
return module.exports.list_images_using_avdmanager(); return module.exports.list_images_using_avdmanager();
} else if (forgivingWhichSync('android')) { } else if (forgivingWhichSync('android')) {
return module.exports.list_images_using_android(); return module.exports.list_images_using_android();
} else { } else {
return Q().then(function() { return Q().then(function () {
throw new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?'); throw new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?');
}); });
} }
@ -187,9 +185,8 @@ module.exports.list_images = function() {
* or undefined if no avds exist. * or undefined if no avds exist.
* Returns a promise. * Returns a promise.
*/ */
module.exports.best_image = function() { module.exports.best_image = function () {
return this.list_images() return this.list_images().then(function (images) {
.then(function(images) {
// Just return undefined if there is no images // Just return undefined if there is no images
if (images.length === 0) return; if (images.length === 0) return;
@ -198,9 +195,9 @@ module.exports.best_image = function() {
var project_target = check_reqs.get_target().replace('android-', ''); var project_target = check_reqs.get_target().replace('android-', '');
for (var i in images) { for (var i in images) {
var target = images[i].target; var target = images[i].target;
if(target) { if (target) {
var num = target.split('(API level ')[1].replace(')', ''); var num = target.split('(API level ')[1].replace(')', '');
if (num == project_target) { if (num === project_target) {
return images[i]; return images[i];
} else if (project_target - num < closest && project_target > num) { } else if (project_target - num < closest && project_target > num) {
closest = project_target - num; closest = project_target - num;
@ -213,19 +210,18 @@ module.exports.best_image = function() {
}; };
// Returns a promise. // Returns a promise.
module.exports.list_started = function() { module.exports.list_started = function () {
return Adb.devices({emulators: true}); return Adb.devices({emulators: true});
}; };
// Returns a promise. // Returns a promise.
// TODO: we should remove this, there's a more robust method under android_sdk.js // TODO: we should remove this, there's a more robust method under android_sdk.js
module.exports.list_targets = function() { module.exports.list_targets = function () {
return superspawn.spawn('android', ['list', 'targets'], {cwd: os.tmpdir()}) return superspawn.spawn('android', ['list', 'targets'], {cwd: os.tmpdir()}).then(function (output) {
.then(function(output) {
var target_out = output.split('\n'); var target_out = output.split('\n');
var targets = []; var targets = [];
for (var i = target_out.length; i >= 0; i--) { for (var i = target_out.length; i >= 0; i--) {
if(target_out[i].match(/id:/)) { if (target_out[i].match(/id:/)) {
targets.push(targets[i].split(' ')[1]); targets.push(targets[i].split(' ')[1]);
} }
} }
@ -240,9 +236,8 @@ module.exports.list_targets = function() {
module.exports.get_available_port = function () { module.exports.get_available_port = function () {
var self = this; var self = this;
return self.list_started() return self.list_started().then(function (emulators) {
.then(function (emulators) { for (var p = 5584; p >= 5554; p -= 2) {
for (var p = 5584; p >= 5554; p-=2) {
if (emulators.indexOf('emulator-' + p) === -1) { if (emulators.indexOf('emulator-' + p) === -1) {
events.emit('verbose', 'Found available port: ' + p); events.emit('verbose', 'Found available port: ' + p);
return p; return p;
@ -262,14 +257,13 @@ module.exports.get_available_port = function () {
* *
* Returns a promise. * Returns a promise.
*/ */
module.exports.start = function(emulator_ID, boot_timeout) { module.exports.start = function (emulator_ID, boot_timeout) {
var self = this; var self = this;
return Q().then(function() { return Q().then(function () {
if (emulator_ID) return Q(emulator_ID); if (emulator_ID) return Q(emulator_ID);
return self.best_image() return self.best_image().then(function (best) {
.then(function(best) {
if (best && best.name) { if (best && best.name) {
events.emit('warn', 'No emulator specified, defaulting to ' + best.name); events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
return best.name; return best.name;
@ -281,9 +275,8 @@ module.exports.start = function(emulator_ID, boot_timeout) {
'2. Create an AVD by running: ' + androidCmd + ' avd\n' + '2. Create an AVD by running: ' + androidCmd + ' avd\n' +
'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n')); 'HINT: For a faster emulator, use an Intel System Image and install the HAXM device driver\n'));
}); });
}).then(function(emulatorId) { }).then(function (emulatorId) {
return self.get_available_port() return self.get_available_port().then(function (port) {
.then(function (port) {
// Figure out the directory the emulator binary runs in, and set the cwd to that directory. // Figure out the directory the emulator binary runs in, and set the cwd to that directory.
// Workaround for https://code.google.com/p/android/issues/detail?id=235461 // Workaround for https://code.google.com/p/android/issues/detail?id=235461
var emulator_dir = path.dirname(shelljs.which('emulator')); var emulator_dir = path.dirname(shelljs.which('emulator'));
@ -297,20 +290,17 @@ module.exports.start = function(emulator_ID, boot_timeout) {
events.emit('log', 'Waiting for emulator to start...'); events.emit('log', 'Waiting for emulator to start...');
return self.wait_for_emulator(port); return self.wait_for_emulator(port);
}); });
}).then(function(emulatorId) { }).then(function (emulatorId) {
if (!emulatorId) if (!emulatorId) { return Q.reject(new CordovaError('Failed to start emulator')); }
return Q.reject(new CordovaError('Failed to start emulator'));
//wait for emulator to boot up // wait for emulator to boot up
process.stdout.write('Waiting for emulator to boot (this may take a while)...'); process.stdout.write('Waiting for emulator to boot (this may take a while)...');
return self.wait_for_boot(emulatorId, boot_timeout) return self.wait_for_boot(emulatorId, boot_timeout).then(function (success) {
.then(function(success) {
if (success) { if (success) {
events.emit('log','BOOT COMPLETE'); events.emit('log', 'BOOT COMPLETE');
//unlock screen // unlock screen
return Adb.shell(emulatorId, 'input keyevent 82') return Adb.shell(emulatorId, 'input keyevent 82').then(function () {
.then(function() { // return the new emulator id for the started emulators
//return the new emulator id for the started emulators
return emulatorId; return emulatorId;
}); });
} else { } else {
@ -325,20 +315,19 @@ module.exports.start = function(emulator_ID, boot_timeout) {
* Waits for an emulator to boot on a given port. * Waits for an emulator to boot on a given port.
* Returns this emulator's ID in a promise. * Returns this emulator's ID in a promise.
*/ */
module.exports.wait_for_emulator = function(port) { module.exports.wait_for_emulator = function (port) {
var self = this; var self = this;
return Q().then(function() { return Q().then(function () {
var emulator_id = 'emulator-' + port; var emulator_id = 'emulator-' + port;
return Adb.shell(emulator_id, 'getprop dev.bootcomplete') return Adb.shell(emulator_id, 'getprop dev.bootcomplete').then(function (output) {
.then(function (output) {
if (output.indexOf('1') >= 0) { if (output.indexOf('1') >= 0) {
return emulator_id; return emulator_id;
} }
return self.wait_for_emulator(port); return self.wait_for_emulator(port);
}, function (error) { }, function (error) {
if (error && error.message && if ((error && error.message &&
(error.message.indexOf('not found') > -1) || (error.message.indexOf('not found') > -1)) ||
error.message.indexOf('device offline') > -1) { (error.message.indexOf('device offline') > -1)) {
// emulator not yet started, continue waiting // emulator not yet started, continue waiting
return self.wait_for_emulator(port); return self.wait_for_emulator(port);
} else { } else {
@ -346,7 +335,7 @@ module.exports.wait_for_emulator = function(port) {
throw error; throw error;
} }
}); });
}); });
}; };
/* /*
@ -354,10 +343,9 @@ module.exports.wait_for_emulator = function(port) {
* promise that resolves to a boolean indicating success. Not specifying a * promise that resolves to a boolean indicating success. Not specifying a
* time_remaining or passing a negative value will cause it to wait forever * time_remaining or passing a negative value will cause it to wait forever
*/ */
module.exports.wait_for_boot = function(emulator_id, time_remaining) { module.exports.wait_for_boot = function (emulator_id, time_remaining) {
var self = this; var self = this;
return Adb.shell(emulator_id, 'ps') return Adb.shell(emulator_id, 'ps').then(function (output) {
.then(function(output) {
if (output.match(/android\.process\.acore/)) { if (output.match(/android\.process\.acore/)) {
return true; return true;
} else if (time_remaining === 0) { } else if (time_remaining === 0) {
@ -366,7 +354,7 @@ module.exports.wait_for_boot = function(emulator_id, time_remaining) {
process.stdout.write('.'); process.stdout.write('.');
// Check at regular intervals // Check at regular intervals
return Q.delay(time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL).then(function() { return Q.delay(time_remaining < CHECK_BOOTED_INTERVAL ? time_remaining : CHECK_BOOTED_INTERVAL).then(function () {
var updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining; var updated_time = time_remaining >= 0 ? Math.max(time_remaining - CHECK_BOOTED_INTERVAL, 0) : time_remaining;
return self.wait_for_boot(emulator_id, updated_time); return self.wait_for_boot(emulator_id, updated_time);
}); });
@ -379,11 +367,10 @@ module.exports.wait_for_boot = function(emulator_id, time_remaining) {
* TODO : Enter the stdin input required to complete the creation of an avd. * TODO : Enter the stdin input required to complete the creation of an avd.
* Returns a promise. * Returns a promise.
*/ */
module.exports.create_image = function(name, target) { module.exports.create_image = function (name, target) {
console.log('Creating new avd named ' + name); console.log('Creating new avd named ' + name);
if (target) { if (target) {
return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', target]) return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', target]).then(null, function (error) {
.then(null, function(error) {
console.error('ERROR : Failed to create emulator image : '); console.error('ERROR : Failed to create emulator image : ');
console.error(' Do you have the latest android targets including ' + target + '?'); console.error(' Do you have the latest android targets including ' + target + '?');
console.error(error); console.error(error);
@ -391,22 +378,20 @@ module.exports.create_image = function(name, target) {
} else { } else {
console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.'); console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
// TODO: there's a more robust method for finding targets in android_sdk.js // TODO: there's a more robust method for finding targets in android_sdk.js
return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]) return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]).then(function () {
.then(function() {
// TODO: This seems like another error case, even though it always happens. // TODO: This seems like another error case, even though it always happens.
console.error('ERROR : Unable to create an avd emulator, no targets found.'); console.error('ERROR : Unable to create an avd emulator, no targets found.');
console.error('Ensure you have targets available by running the "android" command'); console.error('Ensure you have targets available by running the "android" command');
return Q.reject(); return Q.reject();
}, function(error) { }, function (error) {
console.error('ERROR : Failed to create emulator image : '); console.error('ERROR : Failed to create emulator image : ');
console.error(error); console.error(error);
}); });
} }
}; };
module.exports.resolveTarget = function(target) { module.exports.resolveTarget = function (target) {
return this.list_started() return this.list_started().then(function (emulator_list) {
.then(function(emulator_list) {
if (emulator_list.length < 1) { if (emulator_list.length < 1) {
return Q.reject('No running Android emulators found, please start an emulator before deploying your project.'); return Q.reject('No running Android emulators found, please start an emulator before deploying your project.');
} }
@ -417,9 +402,8 @@ module.exports.resolveTarget = function(target) {
return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'); return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
} }
return build.detectArchitecture(target) return build.detectArchitecture(target).then(function (arch) {
.then(function(arch) { return {target: target, arch: arch, isEmulator: true};
return {target:target, arch:arch, isEmulator:true};
}); });
}); });
}; };
@ -430,7 +414,7 @@ module.exports.resolveTarget = function(target) {
* If no started emulators are found, error out. * If no started emulators are found, error out.
* Returns a promise. * Returns a promise.
*/ */
module.exports.install = function(givenTarget, buildResults) { module.exports.install = function (givenTarget, buildResults) {
var target; var target;
var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml')); var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
@ -438,7 +422,7 @@ module.exports.install = function(givenTarget, buildResults) {
// resolve the target emulator // resolve the target emulator
return Q().then(function () { return Q().then(function () {
if (givenTarget && typeof givenTarget == 'object') { if (givenTarget && typeof givenTarget === 'object') {
return givenTarget; return givenTarget;
} else { } else {
return module.exports.resolveTarget(givenTarget); return module.exports.resolveTarget(givenTarget);
@ -452,13 +436,12 @@ module.exports.install = function(givenTarget, buildResults) {
}).then(function () { }).then(function () {
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed. // or the app doesn't installed at all, so no error catching needed.
return Q.when() return Q.when().then(function () {
.then(function() {
var apk_path = build.findBestApkForArchitecture(buildResults, target.arch); var apk_path = build.findBestApkForArchitecture(buildResults, target.arch);
var execOptions = { var execOptions = {
cwd: os.tmpdir(), cwd: os.tmpdir(),
timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds timeout: INSTALL_COMMAND_TIMEOUT, // in milliseconds
killSignal: EXEC_KILL_SIGNAL killSignal: EXEC_KILL_SIGNAL
}; };
@ -469,12 +452,12 @@ module.exports.install = function(givenTarget, buildResults) {
// A special function to call adb install in specific environment w/ specific options. // A special function to call adb install in specific environment w/ specific options.
// Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119 // Introduced as a part of fix for http://issues.apache.org/jira/browse/CB-9119
// to workaround sporadic emulator hangs // to workaround sporadic emulator hangs
function adbInstallWithOptions(target, apk, opts) { function adbInstallWithOptions (target, apk, opts) {
events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...'); events.emit('verbose', 'Installing apk ' + apk + ' on ' + target + '...');
var command = 'adb -s ' + target + ' install -r "' + apk + '"'; var command = 'adb -s ' + target + ' install -r "' + apk + '"';
return Q.promise(function (resolve, reject) { return Q.promise(function (resolve, reject) {
child_process.exec(command, opts, function(err, stdout, stderr) { child_process.exec(command, opts, function (err, stdout, stderr) {
if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr)); if (err) reject(new CordovaError('Error executing "' + command + '": ' + stderr));
// adb does not return an error code even if installation fails. Instead it puts a specific // adb does not return an error code even if installation fails. Instead it puts a specific
// message to stdout, so we have to use RegExp matching to detect installation failure. // message to stdout, so we have to use RegExp matching to detect installation failure.
@ -494,27 +477,23 @@ module.exports.install = function(givenTarget, buildResults) {
} }
function installPromise () { function installPromise () {
return adbInstallWithOptions(target.target, apk_path, execOptions) return adbInstallWithOptions(target.target, apk_path, execOptions).catch(function (error) {
.catch(function (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that // CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate // is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) { throw error; }
throw error;
events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' + events.emit('warn', 'Uninstalling app from device and reinstalling it because the ' +
'currently installed app was signed with different key'); 'currently installed app was signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app // This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed. // or the app doesn't installed at all, so no error catching needed.
return Adb.uninstall(target.target, pkgName) return Adb.uninstall(target.target, pkgName).then(function () {
.then(function() {
return adbInstallWithOptions(target.target, apk_path, execOptions); return adbInstallWithOptions(target.target, apk_path, execOptions);
}); });
}); });
} }
return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise) return retry.retryPromise(NUM_INSTALL_RETRIES, installPromise).then(function (output) {
.then(function (output) {
events.emit('log', 'INSTALL SUCCESS'); events.emit('log', 'INSTALL SUCCESS');
}); });
}); });

View File

@ -0,0 +1,3 @@
@ECHO OFF
for /f "tokens=2*" %%a in ('REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Android Studio" /v Path') do set "ASPath=%%~b"
ECHO %ASPath%

View File

@ -19,28 +19,28 @@
under the License. under the License.
*/ */
var path = require('path'), var path = require('path');
os = require('os'), var os = require('os');
Q = require('q'), var Q = require('q');
child_process = require('child_process'), var child_process = require('child_process');
ROOT = path.join(__dirname, '..', '..'); var ROOT = path.join(__dirname, '..', '..');
/* /*
* Starts running logcat in the shell. * Starts running logcat in the shell.
* Returns a promise. * Returns a promise.
*/ */
module.exports.run = function() { module.exports.run = function () {
var d = Q.defer(); var d = Q.defer();
var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()}); var adb = child_process.spawn('adb', ['logcat'], {cwd: os.tmpdir()});
adb.stdout.on('data', function(data) { adb.stdout.on('data', function (data) {
var lines = data ? data.toString().split('\n') : []; var lines = data ? data.toString().split('\n') : [];
var out = lines.filter(function(x) { return x.indexOf('nativeGetEnabledTags') < 0; }); var out = lines.filter(function (x) { return x.indexOf('nativeGetEnabledTags') < 0; });
console.log(out.join('\n')); console.log(out.join('\n'));
}); });
adb.stderr.on('data', console.error); adb.stderr.on('data', console.error);
adb.on('close', function(code) { adb.on('close', function (code) {
if (code > 0) { if (code > 0) {
d.reject('Failed to run logcat command.'); d.reject('Failed to run logcat command.');
} else d.resolve(); } else d.resolve();
@ -49,7 +49,7 @@ module.exports.run = function() {
return d.promise; return d.promise;
}; };
module.exports.help = function() { module.exports.help = function () {
console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log'))); console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'log')));
console.log('Gives the logcat output on the command line.'); console.log('Gives the logcat output on the command line.');
process.exit(0); process.exit(0);

View File

@ -26,15 +26,15 @@ var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var handlers = { var handlers = {
'source-file':{ 'source-file': {
install:function(obj, plugin, project, options) { install: function (obj, plugin, project, options) {
if (!obj.src) throw new CordovaError(generateAttributeError('src', 'source-file', plugin.id)); if (!obj.src) throw new CordovaError(generateAttributeError('src', 'source-file', plugin.id));
if (!obj.targetDir) throw new CordovaError(generateAttributeError('target-dir', 'source-file', plugin.id)); if (!obj.targetDir) throw new CordovaError(generateAttributeError('target-dir', 'source-file', plugin.id));
var dest = path.join(obj.targetDir, path.basename(obj.src)); var dest = path.join(obj.targetDir, path.basename(obj.src));
if(options && options.android_studio === true) { if (options && options.android_studio === true) {
dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src)); dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
} }
if (options && options.force) { if (options && options.force) {
@ -43,42 +43,42 @@ var handlers = {
copyNewFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link)); copyNewFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
} }
}, },
uninstall:function(obj, plugin, project, options) { uninstall: function (obj, plugin, project, options) {
var dest = path.join(obj.targetDir, path.basename(obj.src)); var dest = path.join(obj.targetDir, path.basename(obj.src));
if(options && options.android_studio === true) { if (options && options.android_studio === true) {
dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src)); dest = path.join('app/src/main/java', obj.targetDir.substring(4), path.basename(obj.src));
} }
deleteJava(project.projectDir, dest); deleteJava(project.projectDir, dest);
} }
}, },
'lib-file':{ 'lib-file': {
install:function(obj, plugin, project, options) { install: function (obj, plugin, project, options) {
var dest = path.join('libs', path.basename(obj.src)); var dest = path.join('libs', path.basename(obj.src));
if(options && options.android_studio === true) { if (options && options.android_studio === true) {
dest = path.join('app/libs', path.basename(obj.src)); dest = path.join('app/libs', path.basename(obj.src));
} }
copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link)); copyFile(plugin.dir, obj.src, project.projectDir, dest, !!(options && options.link));
}, },
uninstall:function(obj, plugin, project, options) { uninstall: function (obj, plugin, project, options) {
var dest = path.join('libs', path.basename(obj.src)); var dest = path.join('libs', path.basename(obj.src));
if(options && options.android_studio === true) { if (options && options.android_studio === true) {
dest = path.join('app/libs', path.basename(obj.src)); dest = path.join('app/libs', path.basename(obj.src));
} }
removeFile(project.projectDir, dest); removeFile(project.projectDir, dest);
} }
}, },
'resource-file':{ 'resource-file': {
install:function(obj, plugin, project, options) { install: function (obj, plugin, project, options) {
copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), !!(options && options.link)); copyFile(plugin.dir, obj.src, project.projectDir, path.normalize(obj.target), !!(options && options.link));
}, },
uninstall:function(obj, plugin, project, options) { uninstall: function (obj, plugin, project, options) {
removeFile(project.projectDir, path.normalize(obj.target)); removeFile(project.projectDir, path.normalize(obj.target));
} }
}, },
'framework': { 'framework': {
install:function(obj, plugin, project, options) { install: function (obj, plugin, project, options) {
var src = obj.src; var src = obj.src;
if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id)); if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
@ -95,15 +95,15 @@ var handlers = {
subDir = src; subDir = src;
} }
if (obj.type == 'gradleReference') { if (obj.type === 'gradleReference') {
project.addGradleReference(parentDir, subDir); project.addGradleReference(parentDir, subDir);
} else if (obj.type == 'sys') { } else if (obj.type === 'sys') {
project.addSystemLibrary(parentDir, subDir); project.addSystemLibrary(parentDir, subDir);
} else { } else {
project.addSubProject(parentDir, subDir); project.addSubProject(parentDir, subDir);
} }
}, },
uninstall:function(obj, plugin, project, options) { uninstall: function (obj, plugin, project, options) {
var src = obj.src; var src = obj.src;
if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id)); if (!src) throw new CordovaError(generateAttributeError('src', 'framework', plugin.id));
@ -125,17 +125,17 @@ var handlers = {
subDir = src; subDir = src;
} }
if (obj.type == 'gradleReference') { if (obj.type === 'gradleReference') {
project.removeGradleReference(parentDir, subDir); project.removeGradleReference(parentDir, subDir);
} else if (obj.type == 'sys') { } else if (obj.type === 'sys') {
project.removeSystemLibrary(parentDir, subDir); project.removeSystemLibrary(parentDir, subDir);
} else { } else {
project.removeSubProject(parentDir, subDir); project.removeSubProject(parentDir, subDir);
} }
} }
}, },
asset:{ asset: {
install:function(obj, plugin, project, options) { install: function (obj, plugin, project, options) {
if (!obj.src) { if (!obj.src) {
throw new CordovaError(generateAttributeError('src', 'asset', plugin.id)); throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
} }
@ -149,7 +149,7 @@ var handlers = {
copyFile(plugin.dir, obj.src, project.platformWww, obj.target); copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
} }
}, },
uninstall:function(obj, plugin, project, options) { uninstall: function (obj, plugin, project, options) {
var target = obj.target || obj.src; var target = obj.target || obj.src;
if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id)); if (!target) throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
@ -167,7 +167,7 @@ var handlers = {
install: function (obj, plugin, project, options) { install: function (obj, plugin, project, options) {
// Copy the plugin's files into the www directory. // Copy the plugin's files into the www directory.
var moduleSource = path.resolve(plugin.dir, obj.src); var moduleSource = path.resolve(plugin.dir, obj.src);
var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src))); var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname(obj.src)));
// Read in the file, prepend the cordova.define, and write it back out. // Read in the file, prepend the cordova.define, and write it back out.
var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
@ -206,7 +206,7 @@ module.exports.getInstaller = function (type) {
events.emit('verbose', '<' + type + '> is not supported for android plugins'); events.emit('verbose', '<' + type + '> is not supported for android plugins');
}; };
module.exports.getUninstaller = function(type) { module.exports.getUninstaller = function (type) {
if (handlers[type] && handlers[type].uninstall) { if (handlers[type] && handlers[type].uninstall) {
return handlers[type].uninstall; return handlers[type].uninstall;
} }
@ -221,21 +221,19 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
// check that src path is inside plugin directory // check that src path is inside plugin directory
var real_path = fs.realpathSync(src); var real_path = fs.realpathSync(src);
var real_plugin_path = fs.realpathSync(plugin_dir); var real_plugin_path = fs.realpathSync(plugin_dir);
if (real_path.indexOf(real_plugin_path) !== 0) if (real_path.indexOf(real_plugin_path) !== 0) { throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"'); }
throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
dest = path.resolve(project_dir, dest); dest = path.resolve(project_dir, dest);
// check that dest path is located in project directory // check that dest path is located in project directory
if (dest.indexOf(project_dir) !== 0) if (dest.indexOf(project_dir) !== 0) { throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project'); }
throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
shell.mkdir('-p', path.dirname(dest)); shell.mkdir('-p', path.dirname(dest));
if (link) { if (link) {
symlinkFileOrDirTree(src, dest); symlinkFileOrDirTree(src, dest);
} else if (fs.statSync(src).isDirectory()) { } else if (fs.statSync(src).isDirectory()) {
// XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
shell.cp('-Rf', src+'/*', dest); shell.cp('-Rf', src + '/*', dest);
} else { } else {
shell.cp('-f', src, dest); shell.cp('-f', src, dest);
} }
@ -244,24 +242,22 @@ function copyFile (plugin_dir, src, project_dir, dest, link) {
// Same as copy file but throws error if target exists // Same as copy file but throws error if target exists
function copyNewFile (plugin_dir, src, project_dir, dest, link) { function copyNewFile (plugin_dir, src, project_dir, dest, link) {
var target_path = path.resolve(project_dir, dest); var target_path = path.resolve(project_dir, dest);
if (fs.existsSync(target_path)) if (fs.existsSync(target_path)) { throw new CordovaError('"' + target_path + '" already exists!'); }
throw new CordovaError('"' + target_path + '" already exists!');
copyFile(plugin_dir, src, project_dir, dest, !!link); copyFile(plugin_dir, src, project_dir, dest, !!link);
} }
function symlinkFileOrDirTree(src, dest) { function symlinkFileOrDirTree (src, dest) {
if (fs.existsSync(dest)) { if (fs.existsSync(dest)) {
shell.rm('-Rf', dest); shell.rm('-Rf', dest);
} }
if (fs.statSync(src).isDirectory()) { if (fs.statSync(src).isDirectory()) {
shell.mkdir('-p', dest); shell.mkdir('-p', dest);
fs.readdirSync(src).forEach(function(entry) { fs.readdirSync(src).forEach(function (entry) {
symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry)); symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
}); });
} } else {
else {
fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest); fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest);
} }
} }
@ -292,8 +288,8 @@ function removeFileAndParents (baseDir, destFile, stopper) {
// check if directory is empty // check if directory is empty
var curDir = path.dirname(file); var curDir = path.dirname(file);
while(curDir !== path.resolve(baseDir, stopper)) { while (curDir !== path.resolve(baseDir, stopper)) {
if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) { if (fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
fs.rmdirSync(curDir); fs.rmdirSync(curDir);
curDir = path.resolve(curDir, '..'); curDir = path.resolve(curDir, '..');
} else { } else {
@ -303,6 +299,6 @@ function removeFileAndParents (baseDir, destFile, stopper) {
} }
} }
function generateAttributeError(attribute, element, id) { function generateAttributeError (attribute, element, id) {
return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id; return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
} }

View File

@ -16,6 +16,7 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* eslint no-useless-escape: 0 */
var Q = require('q'); var Q = require('q');
var fs = require('fs'); var fs = require('fs');
@ -40,17 +41,14 @@ module.exports.prepare = function (cordovaProject, options) {
this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations); this._config = updateConfigFilesFrom(cordovaProject.projectConfig, munger, this.locations);
// Update own www dir with project's www assets and plugins' assets and js-files // Update own www dir with project's www assets and plugins' assets and js-files
return Q.when(updateWww(cordovaProject, this.locations)) return Q.when(updateWww(cordovaProject, this.locations)).then(function () {
.then(function () {
// update project according to config.xml changes. // update project according to config.xml changes.
return updateProjectAccordingTo(self._config, self.locations); return updateProjectAccordingTo(self._config, self.locations);
}) }).then(function () {
.then(function () {
updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res)); updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res)); updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res));
updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root)); updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root));
}) }).then(function () {
.then(function () {
events.emit('verbose', 'Prepared android project successfully'); events.emit('verbose', 'Prepared android project successfully');
}); });
}; };
@ -91,7 +89,7 @@ module.exports.clean = function (options) {
* represents current project's configuration. When returned, the * represents current project's configuration. When returned, the
* configuration is already dumped to appropriate config.xml file. * configuration is already dumped to appropriate config.xml file.
*/ */
function updateConfigFilesFrom(sourceConfig, configMunger, locations) { function updateConfigFilesFrom (sourceConfig, configMunger, locations) {
events.emit('verbose', 'Generating platform-specific config.xml from defaults for android at ' + locations.configXml); events.emit('verbose', 'Generating platform-specific config.xml from defaults for android at ' + locations.configXml);
// First cleanup current config and merge project's one into own // First cleanup current config and merge project's one into own
@ -106,7 +104,7 @@ function updateConfigFilesFrom(sourceConfig, configMunger, locations) {
// Merge changes from app's config.xml into platform's one // Merge changes from app's config.xml into platform's one
var config = new ConfigParser(locations.configXml); var config = new ConfigParser(locations.configXml);
xmlHelpers.mergeXml(sourceConfig.doc.getroot(), xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
config.doc.getroot(), 'android', /*clobber=*/true); config.doc.getroot(), 'android', /* clobber= */true);
config.write(); config.write();
return config; return config;
@ -115,7 +113,7 @@ function updateConfigFilesFrom(sourceConfig, configMunger, locations) {
/** /**
* Logs all file operations via the verbose event stream, indented. * Logs all file operations via the verbose event stream, indented.
*/ */
function logFileOp(message) { function logFileOp (message) {
events.emit('verbose', ' ' + message); events.emit('verbose', ' ' + message);
} }
@ -128,7 +126,7 @@ function logFileOp(message) {
* @param {Object} destinations An object that contains destination * @param {Object} destinations An object that contains destination
* paths for www files. * paths for www files.
*/ */
function updateWww(cordovaProject, destinations) { function updateWww (cordovaProject, destinations) {
var sourceDirs = [ var sourceDirs = [
path.relative(cordovaProject.root, cordovaProject.locations.www), path.relative(cordovaProject.root, cordovaProject.locations.www),
path.relative(cordovaProject.root, destinations.platformWww) path.relative(cordovaProject.root, destinations.platformWww)
@ -151,7 +149,7 @@ function updateWww(cordovaProject, destinations) {
/** /**
* Cleans all files from the platform 'www' directory. * Cleans all files from the platform 'www' directory.
*/ */
function cleanWww(projectRoot, locations) { function cleanWww (projectRoot, locations) {
var targetDir = path.relative(projectRoot, locations.www); var targetDir = path.relative(projectRoot, locations.www);
events.emit('verbose', 'Cleaning ' + targetDir); events.emit('verbose', 'Cleaning ' + targetDir);
@ -167,7 +165,7 @@ function cleanWww(projectRoot, locations) {
* be used to update project * be used to update project
* @param {Object} locations A map of locations for this platform * @param {Object} locations A map of locations for this platform
*/ */
function updateProjectAccordingTo(platformConfig, locations) { function updateProjectAccordingTo (platformConfig, locations) {
// Update app name by editing res/values/strings.xml // Update app name by editing res/values/strings.xml
var name = platformConfig.name(); var name = platformConfig.name();
var strings = xmlHelpers.parseElementtreeSync(locations.strings); var strings = xmlHelpers.parseElementtreeSync(locations.strings);
@ -202,7 +200,7 @@ function updateProjectAccordingTo(platformConfig, locations) {
if (java_files.length === 0) { if (java_files.length === 0) {
throw new CordovaError('No Java files found that extend CordovaActivity.'); throw new CordovaError('No Java files found that extend CordovaActivity.');
} else if(java_files.length > 1) { } else if (java_files.length > 1) {
events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]); events.emit('log', 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java_files[0]);
} }
@ -213,12 +211,12 @@ function updateProjectAccordingTo(platformConfig, locations) {
if (orig_pkg !== pkg) { if (orig_pkg !== pkg) {
// If package was name changed we need to remove old java with main activity // If package was name changed we need to remove old java with main activity
shell.rm('-Rf',java_files[0]); shell.rm('-Rf', java_files[0]);
// remove any empty directories // remove any empty directories
var currentDir = path.dirname(java_files[0]); var currentDir = path.dirname(java_files[0]);
var sourcesRoot = path.resolve(locations.root, 'src'); var sourcesRoot = path.resolve(locations.root, 'src');
while(currentDir !== sourcesRoot) { while (currentDir !== sourcesRoot) {
if(fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) { if (fs.existsSync(currentDir) && fs.readdirSync(currentDir).length === 0) {
fs.rmdirSync(currentDir); fs.rmdirSync(currentDir);
currentDir = path.resolve(currentDir, '..'); currentDir = path.resolve(currentDir, '..');
} else { } else {
@ -231,7 +229,7 @@ function updateProjectAccordingTo(platformConfig, locations) {
// Consturct the default value for versionCode as // Consturct the default value for versionCode as
// PATCH + MINOR * 100 + MAJOR * 10000 // PATCH + MINOR * 100 + MAJOR * 10000
// see http://developer.android.com/tools/publishing/versioning.html // see http://developer.android.com/tools/publishing/versioning.html
function default_versionCode(version) { function default_versionCode (version) {
var nums = version.split('-')[0].split('.'); var nums = version.split('-')[0].split('.');
var versionCode = 0; var versionCode = 0;
if (+nums[0]) { if (+nums[0]) {
@ -248,7 +246,7 @@ function default_versionCode(version) {
return versionCode; return versionCode;
} }
function getImageResourcePath(resourcesDir, type, density, name, sourceName) { function getImageResourcePath (resourcesDir, type, density, name, sourceName) {
if (/\.9\.png$/.test(sourceName)) { if (/\.9\.png$/.test(sourceName)) {
name = name.replace(/\.png$/, '.9.png'); name = name.replace(/\.png$/, '.9.png');
} }
@ -256,7 +254,7 @@ function getImageResourcePath(resourcesDir, type, density, name, sourceName) {
return resourcePath; return resourcePath;
} }
function updateSplashes(cordovaProject, platformResourcesDir) { function updateSplashes (cordovaProject, platformResourcesDir) {
var resources = cordovaProject.projectConfig.getSplashScreens('android'); var resources = cordovaProject.projectConfig.getSplashScreens('android');
// if there are "splash" elements in config.xml // if there are "splash" elements in config.xml
@ -272,7 +270,7 @@ function updateSplashes(cordovaProject, platformResourcesDir) {
if (!resource.density) { if (!resource.density) {
return; return;
} }
if (resource.density == 'mdpi') { if (resource.density === 'mdpi') {
hadMdpi = true; hadMdpi = true;
} }
var targetPath = getImageResourcePath( var targetPath = getImageResourcePath(
@ -292,7 +290,7 @@ function updateSplashes(cordovaProject, platformResourcesDir) {
resourceMap, { rootDir: cordovaProject.root }, logFileOp); resourceMap, { rootDir: cordovaProject.root }, logFileOp);
} }
function cleanSplashes(projectRoot, projectConfig, platformResourcesDir) { function cleanSplashes (projectRoot, projectConfig, platformResourcesDir) {
var resources = projectConfig.getSplashScreens('android'); var resources = projectConfig.getSplashScreens('android');
if (resources.length > 0) { if (resources.length > 0) {
var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png'); var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'drawable', 'screen.png');
@ -304,7 +302,7 @@ function cleanSplashes(projectRoot, projectConfig, platformResourcesDir) {
} }
} }
function updateIcons(cordovaProject, platformResourcesDir) { function updateIcons (cordovaProject, platformResourcesDir) {
var icons = cordovaProject.projectConfig.getIcons('android'); var icons = cordovaProject.projectConfig.getIcons('android');
// if there are icon elements in config.xml // if there are icon elements in config.xml
@ -328,7 +326,7 @@ function updateIcons(cordovaProject, platformResourcesDir) {
}; };
// find the best matching icon for a given density or size // find the best matching icon for a given density or size
// @output android_icons // @output android_icons
var parseIcon = function(icon, icon_size) { var parseIcon = function (icon, icon_size) {
// do I have a platform icon for that density already // do I have a platform icon for that density already
var density = icon.density || sizeToDensityMap[icon_size]; var density = icon.density || sizeToDensityMap[icon_size];
if (!density) { if (!density) {
@ -343,7 +341,7 @@ function updateIcons(cordovaProject, platformResourcesDir) {
}; };
// iterate over all icon elements to find the default icon and call parseIcon // iterate over all icon elements to find the default icon and call parseIcon
for (var i=0; i<icons.length; i++) { for (var i = 0; i < icons.length; i++) {
var icon = icons[i]; var icon = icons[i];
var size = icon.width; var size = icon.width;
if (!size) { if (!size) {
@ -380,7 +378,7 @@ function updateIcons(cordovaProject, platformResourcesDir) {
resourceMap, { rootDir: cordovaProject.root }, logFileOp); resourceMap, { rootDir: cordovaProject.root }, logFileOp);
} }
function cleanIcons(projectRoot, projectConfig, platformResourcesDir) { function cleanIcons (projectRoot, projectConfig, platformResourcesDir) {
var icons = projectConfig.getIcons('android'); var icons = projectConfig.getIcons('android');
if (icons.length > 0) { if (icons.length > 0) {
var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png'); var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png');
@ -395,18 +393,16 @@ function cleanIcons(projectRoot, projectConfig, platformResourcesDir) {
/** /**
* Gets a map containing resources of a specified name from all drawable folders in a directory. * Gets a map containing resources of a specified name from all drawable folders in a directory.
*/ */
function mapImageResources(rootDir, subDir, type, resourceName) { function mapImageResources (rootDir, subDir, type, resourceName) {
var pathMap = {}; var pathMap = {};
shell.ls(path.join(rootDir, subDir, type + '-*')) shell.ls(path.join(rootDir, subDir, type + '-*')).forEach(function (drawableFolder) {
.forEach(function (drawableFolder) {
var imagePath = path.join(subDir, path.basename(drawableFolder), resourceName); var imagePath = path.join(subDir, path.basename(drawableFolder), resourceName);
pathMap[imagePath] = null; pathMap[imagePath] = null;
}); });
return pathMap; return pathMap;
} }
function updateFileResources (cordovaProject, platformDir) {
function updateFileResources(cordovaProject, platformDir) {
var files = cordovaProject.projectConfig.getFileResources('android'); var files = cordovaProject.projectConfig.getFileResources('android');
// if there are resource-file elements in config.xml // if there are resource-file elements in config.xml
@ -416,7 +412,7 @@ function updateFileResources(cordovaProject, platformDir) {
} }
var resourceMap = {}; var resourceMap = {};
files.forEach(function(res) { files.forEach(function (res) {
var targetPath = path.join(platformDir, res.target); var targetPath = path.join(platformDir, res.target);
resourceMap[targetPath] = res.src; resourceMap[targetPath] = res.src;
}); });
@ -426,20 +422,20 @@ function updateFileResources(cordovaProject, platformDir) {
resourceMap, { rootDir: cordovaProject.root }, logFileOp); resourceMap, { rootDir: cordovaProject.root }, logFileOp);
} }
function cleanFileResources (projectRoot, projectConfig, platformDir) {
function cleanFileResources(projectRoot, projectConfig, platformDir) {
var files = projectConfig.getFileResources('android', true); var files = projectConfig.getFileResources('android', true);
if (files.length > 0) { if (files.length > 0) {
events.emit('verbose', 'Cleaning resource files at ' + platformDir); events.emit('verbose', 'Cleaning resource files at ' + platformDir);
var resourceMap = {}; var resourceMap = {};
files.forEach(function(res) { files.forEach(function (res) {
var filePath = path.join(platformDir, res.target); var filePath = path.join(platformDir, res.target);
resourceMap[filePath] = null; resourceMap[filePath] = null;
}); });
FileUpdater.updatePaths( FileUpdater.updatePaths(
resourceMap, { rootDir: projectRoot, all: true}, logFileOp); resourceMap, {
rootDir: projectRoot, all: true}, logFileOp);
} }
} }
@ -454,7 +450,7 @@ function cleanFileResources(projectRoot, projectConfig, platformDir) {
* default value, if there is no such preference. The default value is * default value, if there is no such preference. The default value is
* 'singleTop' * 'singleTop'
*/ */
function findAndroidLaunchModePreference(platformConfig) { function findAndroidLaunchModePreference (platformConfig) {
var launchMode = platformConfig.getPreference('AndroidLaunchMode'); var launchMode = platformConfig.getPreference('AndroidLaunchMode');
if (!launchMode) { if (!launchMode) {
// Return a default value // Return a default value

View File

@ -45,12 +45,12 @@ module.exports.retryPromise = function (attemts_left, promiseFunction) {
return promiseFunction.apply(undefined, promiseFunctionArguments).then( return promiseFunction.apply(undefined, promiseFunctionArguments).then(
// on success pass results through // on success pass results through
function onFulfilled(value) { function onFulfilled (value) {
return value; return value;
}, },
// on rejection either retry, or throw the error // on rejection either retry, or throw the error
function onRejected(error) { function onRejected (error) {
attemts_left -= 1; attemts_left -= 1;

View File

@ -21,14 +21,14 @@
/* jshint loopfunc:true */ /* jshint loopfunc:true */
var path = require('path'), var path = require('path');
build = require('./build'), var build = require('./build');
emulator = require('./emulator'), var emulator = require('./emulator');
device = require('./device'), var device = require('./device');
Q = require('q'), var Q = require('q');
events = require('cordova-common').events; var events = require('cordova-common').events;
function getInstallTarget(runOptions) { function getInstallTarget (runOptions) {
var install_target; var install_target;
if (runOptions.target) { if (runOptions.target) {
install_target = runOptions.target; install_target = runOptions.target;
@ -51,17 +51,15 @@ function getInstallTarget(runOptions) {
* *
* @return {Promise} * @return {Promise}
*/ */
module.exports.run = function(runOptions) { module.exports.run = function (runOptions) {
var self = this; var self = this;
var install_target = getInstallTarget(runOptions); var install_target = getInstallTarget(runOptions);
return Q() return Q().then(function () {
.then(function() {
if (!install_target) { if (!install_target) {
// no target given, deploy to device if available, otherwise use the emulator. // no target given, deploy to device if available, otherwise use the emulator.
return device.list() return device.list().then(function (device_list) {
.then(function(device_list) {
if (device_list.length > 0) { if (device_list.length > 0) {
events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.'); events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0]; install_target = device_list[0];
@ -71,36 +69,31 @@ function getInstallTarget(runOptions) {
} }
}); });
} }
}).then(function() { }).then(function () {
if (install_target == '--device') { if (install_target === '--device') {
return device.resolveTarget(null); return device.resolveTarget(null);
} else if (install_target == '--emulator') { } else if (install_target === '--emulator') {
// Give preference to any already started emulators. Else, start one. // Give preference to any already started emulators. Else, start one.
return emulator.list_started() return emulator.list_started().then(function (started) {
.then(function(started) {
return started && started.length > 0 ? started[0] : emulator.start(); return started && started.length > 0 ? started[0] : emulator.start();
}).then(function(emulatorId) { }).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId); return emulator.resolveTarget(emulatorId);
}); });
} }
// They specified a specific device/emulator ID. // They specified a specific device/emulator ID.
return device.list() return device.list().then(function (devices) {
.then(function(devices) {
if (devices.indexOf(install_target) > -1) { if (devices.indexOf(install_target) > -1) {
return device.resolveTarget(install_target); return device.resolveTarget(install_target);
} }
return emulator.list_started() return emulator.list_started().then(function (started_emulators) {
.then(function(started_emulators) {
if (started_emulators.indexOf(install_target) > -1) { if (started_emulators.indexOf(install_target) > -1) {
return emulator.resolveTarget(install_target); return emulator.resolveTarget(install_target);
} }
return emulator.list_images() return emulator.list_images().then(function (avds) {
.then(function(avds) {
// if target emulator isn't started, then start it. // if target emulator isn't started, then start it.
for (var avd in avds) { for (var avd in avds) {
if (avds[avd].name == install_target) { if (avds[avd].name === install_target) {
return emulator.start(install_target) return emulator.start(install_target).then(function (emulatorId) {
.then(function(emulatorId) {
return emulator.resolveTarget(emulatorId); return emulator.resolveTarget(emulatorId);
}); });
} }
@ -109,16 +102,14 @@ function getInstallTarget(runOptions) {
}); });
}); });
}); });
}).then(function(resolvedTarget) { }).then(function (resolvedTarget) {
// Better just call self.build, but we're doing some processing of // Better just call self.build, but we're doing some processing of
// build results (according to platformApi spec) so they are in different // build results (according to platformApi spec) so they are in different
// format than emulator.install expects. // format than emulator.install expects.
// TODO: Update emulator/device.install to handle this change // TODO: Update emulator/device.install to handle this change
return build.run.call(self, runOptions, resolvedTarget) return build.run.call(self, runOptions, resolvedTarget).then(function (buildResults) {
.then(function(buildResults) {
if (resolvedTarget.isEmulator) { if (resolvedTarget.isEmulator) {
return emulator.wait_for_boot(resolvedTarget.target) return emulator.wait_for_boot(resolvedTarget.target).then(function () {
.then(function () {
return emulator.install(resolvedTarget, buildResults); return emulator.install(resolvedTarget, buildResults);
}); });
} }
@ -127,7 +118,7 @@ function getInstallTarget(runOptions) {
}); });
}; };
module.exports.help = function() { module.exports.help = function () {
console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]) + ' [options]'); console.log('Usage: ' + path.relative(process.cwd(), process.argv[1]) + ' [options]');
console.log('Build options :'); console.log('Build options :');
console.log(' --debug : Builds project in debug mode'); console.log(' --debug : Builds project in debug mode');

File diff suppressed because it is too large Load Diff

View File

@ -18,25 +18,25 @@
*/ */
var app = { var app = {
// Application Constructor // Application Constructor
initialize: function() { initialize: function () {
this.bindEvents(); this.bindEvents();
}, },
// Bind Event Listeners // Bind Event Listeners
// //
// Bind any events that are required on startup. Common events are: // Bind any events that are required on startup. Common events are:
// 'load', 'deviceready', 'offline', and 'online'. // 'load', 'deviceready', 'offline', and 'online'.
bindEvents: function() { bindEvents: function () {
document.addEventListener('deviceready', this.onDeviceReady, false); document.addEventListener('deviceready', this.onDeviceReady, false);
}, },
// deviceready Event Handler // deviceready Event Handler
// //
// The scope of 'this' is the event. In order to call the 'receivedEvent' // The scope of 'this' is the event. In order to call the 'receivedEvent'
// function, we must explicitly call 'app.receivedEvent(...);' // function, we must explicitly call 'app.receivedEvent(...);'
onDeviceReady: function() { onDeviceReady: function () {
app.receivedEvent('deviceready'); app.receivedEvent('deviceready');
}, },
// Update DOM on a Received Event // Update DOM on a Received Event
receivedEvent: function(id) { receivedEvent: function (id) {
var parentElement = document.getElementById(id); var parentElement = document.getElementById(id);
var listeningElement = parentElement.querySelector('.listening'); var listeningElement = parentElement.querySelector('.listening');
var receivedElement = parentElement.querySelector('.received'); var receivedElement = parentElement.querySelector('.received');

View File

@ -19,10 +19,10 @@
"apache" "apache"
], ],
"scripts": { "scripts": {
"test": "npm run jshint && jasmine", "test": "npm run eslint && jasmine",
"cover": "istanbul cover --root bin/templates/cordova --print detail jasmine", "cover": "istanbul cover --root bin/templates/cordova --print detail jasmine",
"test-build": "jasmine --captureExceptions --color spec/e2e/*.spec.js", "test-build": "jasmine --captureExceptions --color spec/e2e/*.spec.js",
"jshint": "jshint bin && jshint spec" "eslint": "eslint bin && eslint spec"
}, },
"author": "Apache Software Foundation", "author": "Apache Software Foundation",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -43,9 +43,15 @@
"shelljs" "shelljs"
], ],
"devDependencies": { "devDependencies": {
"eslint": "^3.19.0",
"eslint-config-semistandard": "^11.0.0",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-import": "^2.3.0",
"eslint-plugin-node": "^5.0.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"istanbul": "^0.4.2", "istanbul": "^0.4.2",
"jasmine": "^2.5.2", "jasmine": "^2.5.2",
"jshint": "^2.6.0",
"promise-matchers": "~0", "promise-matchers": "~0",
"rewire": "^2.1.3" "rewire": "^2.1.3"
}, },

2
spec/.eslintrc.yml Normal file
View File

@ -0,0 +1,2 @@
env:
jasmine: true

View File

@ -21,7 +21,7 @@ var actions = require('./helpers/projectActions.js');
var CREATE_TIMEOUT = 180000; var CREATE_TIMEOUT = 180000;
function createAndBuild(projectname, projectid, done) { function createAndBuild (projectname, projectid, done) {
actions.createProject(projectname, projectid, function (error) { actions.createProject(projectname, projectid, function (error) {
expect(error).toBe(null); expect(error).toBe(null);
actions.buildProject(projectid, function (error) { actions.buildProject(projectid, function (error) {
@ -32,45 +32,44 @@ function createAndBuild(projectname, projectid, done) {
}); });
} }
describe('create', function () {
describe('create', function() { it('Test#001 : create project with ascii name, no spaces', function (done) {
it('Test#001 : create project with ascii name, no spaces', function(done) {
var projectname = 'testcreate'; var projectname = 'testcreate';
var projectid = 'com.test.create.app1'; var projectid = 'com.test.create.app1';
createAndBuild(projectname, projectid, done); createAndBuild(projectname, projectid, done);
}, CREATE_TIMEOUT); }, CREATE_TIMEOUT);
it('Test#002 : create project with ascii name, and spaces', function(done) { it('Test#002 : create project with ascii name, and spaces', function (done) {
var projectname = 'test create'; var projectname = 'test create';
var projectid = 'com.test.create.app2'; var projectid = 'com.test.create.app2';
createAndBuild(projectname, projectid, done); createAndBuild(projectname, projectid, done);
}, CREATE_TIMEOUT); }, CREATE_TIMEOUT);
it('Test#003 : create project with unicode name, no spaces', function(done) { it('Test#003 : create project with unicode name, no spaces', function (done) {
var projectname = '応応応応用用用用'; var projectname = '応応応応用用用用';
var projectid = 'com.test.create.app3'; var projectid = 'com.test.create.app3';
createAndBuild(projectname, projectid, done); createAndBuild(projectname, projectid, done);
}, CREATE_TIMEOUT); }, CREATE_TIMEOUT);
it('Test#004 : create project with unicode name, and spaces', function(done) { it('Test#004 : create project with unicode name, and spaces', function (done) {
var projectname = '応応応応 用用用用'; var projectname = '応応応応 用用用用';
var projectid = 'com.test.create.app4'; var projectid = 'com.test.create.app4';
createAndBuild(projectname, projectid, done); createAndBuild(projectname, projectid, done);
}, CREATE_TIMEOUT); }, CREATE_TIMEOUT);
it('Test#005 : create project with ascii+unicode name, no spaces', function(done) { it('Test#005 : create project with ascii+unicode name, no spaces', function (done) {
var projectname = '応応応応hello用用用用'; var projectname = '応応応応hello用用用用';
var projectid = 'com.test.create.app5'; var projectid = 'com.test.create.app5';
createAndBuild(projectname, projectid, done); createAndBuild(projectname, projectid, done);
}, CREATE_TIMEOUT); }, CREATE_TIMEOUT);
it('Test#006 : create project with ascii+unicode name, and spaces', function(done) { it('Test#006 : create project with ascii+unicode name, and spaces', function (done) {
var projectname = '応応応応 hello 用用用用'; var projectname = '応応応応 hello 用用用用';
var projectid = 'com.test.create.app6'; var projectid = 'com.test.create.app6';

View File

@ -17,11 +17,11 @@
under the License. under the License.
*/ */
var PluginInfoProvider = require('cordova-common').PluginInfoProvider, var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
shell = require('shelljs'), var shell = require('shelljs');
cp = require('child_process'), var cp = require('child_process');
path = require('path'), var path = require('path');
util = require('util'); var util = require('util');
var cordova_bin = path.join(__dirname, '../../../bin'); var cordova_bin = path.join(__dirname, '../../../bin');
@ -115,7 +115,7 @@ module.exports.removeProject = function (projectid) {
* @param {function} callback - function which is called (without arguments) when the plugin is added or (with error object) when error occurs * @param {function} callback - function which is called (without arguments) when the plugin is added or (with error object) when error occurs
*/ */
module.exports.addPlugin = function (projectid, plugindir, callback) { module.exports.addPlugin = function (projectid, plugindir, callback) {
var projectDirName = getDirName(projectid); var projectDirName = getDirName(projectid);
var pip = new PluginInfoProvider(); var pip = new PluginInfoProvider();
var pluginInfo = pip.get(plugindir); var pluginInfo = pip.get(plugindir);
var Api = require(path.join(__dirname, '../../..', projectDirName, 'cordova', 'Api.js')); var Api = require(path.join(__dirname, '../../..', projectDirName, 'cordova', 'Api.js'));
@ -146,6 +146,6 @@ module.exports.getPlatformVersion = function (projectid, callback) {
}); });
}; };
function getDirName(projectid) { function getDirName (projectid) {
return 'test-' + projectid; return 'test-' + projectid;
} }

View File

@ -17,14 +17,14 @@
under the License. under the License.
*/ */
var path = require('path'), var path = require('path');
actions = require('./helpers/projectActions.js'); var actions = require('./helpers/projectActions.js');
var PLUGIN_ADD_TIMEOUT = 90000; var PLUGIN_ADD_TIMEOUT = 90000;
describe('plugin add', function() { describe('plugin add', function () {
it('Test#001 : create project and add a plugin with framework', function(done) { it('Test#001 : create project and add a plugin with framework', function (done) {
var projectname = 'testpluginframework'; var projectname = 'testpluginframework';
var projectid = 'com.test.plugin.framework'; var projectid = 'com.test.plugin.framework';
var fakePluginPath = path.join(__dirname, 'fixtures/cordova-plugin-fake'); var fakePluginPath = path.join(__dirname, 'fixtures/cordova-plugin-fake');

View File

@ -17,22 +17,22 @@
under the License. under the License.
*/ */
var actions = require('./helpers/projectActions.js'), var actions = require('./helpers/projectActions.js');
shell = require('shelljs'), var shell = require('shelljs');
fs = require('fs'), var fs = require('fs');
util = require('util'), var util = require('util');
platformOld = { version: '4.0.0', path: 'cordova-android-old' }, var platformOld = { version: '4.0.0', path: 'cordova-android-old' };
platformEdge = { version: getCurrentVersion(), path: '.' }; var platformEdge = { version: getCurrentVersion(), path: '.' };
var DOWNLOAD_TIMEOUT = 2 * 60 * 1000, var DOWNLOAD_TIMEOUT = 2 * 60 * 1000;
UPDATE_TIMEOUT = 90 * 1000, var UPDATE_TIMEOUT = 90 * 1000;
PLATFORM_GIT_URL = 'https://github.com/apache/cordova-android'; var PLATFORM_GIT_URL = 'https://github.com/apache/cordova-android';
function getCurrentVersion() { function getCurrentVersion () {
return fs.readFileSync('VERSION').toString().trim(); return fs.readFileSync('VERSION').toString().trim();
} }
function testUpdate(projectname, projectid, createfrom, updatefrom, doBuild, done) { function testUpdate (projectname, projectid, createfrom, updatefrom, doBuild, done) {
actions.createProject(projectname, projectid, createfrom.path, function (error) { actions.createProject(projectname, projectid, createfrom.path, function (error) {
expect(error).toBe(null); expect(error).toBe(null);
actions.updateProject(projectid, updatefrom.path, function (error) { actions.updateProject(projectid, updatefrom.path, function (error) {
@ -68,9 +68,9 @@ describe('preparing fixtures', function () {
}); });
describe('update', function() { describe('update', function () {
it('Test#002 : should update major version and build the project', function(done) { it('Test#002 : should update major version and build the project', function (done) {
var projectname = 'testupdate'; var projectname = 'testupdate';
var projectid = 'com.test.update.app1'; var projectid = 'com.test.update.app1';
@ -82,7 +82,7 @@ describe('update', function() {
describe('cleanup', function () { describe('cleanup', function () {
it('Test#004 : remove cloned old platform', function() { it('Test#004 : remove cloned old platform', function () {
shell.rm('-rf', platformOld.path); shell.rm('-rf', platformOld.path);
}); });

View File

@ -1 +0,0 @@
./org.test.plugins.dummyplugin/www/dummyplugin.js

View File

@ -21,9 +21,9 @@ var path = require('path');
var AndroidProject = require('../../bin/templates/cordova/lib/AndroidProject'); var AndroidProject = require('../../bin/templates/cordova/lib/AndroidProject');
var android_project = path.join(__dirname, '../fixtures/android_project'); var android_project = path.join(__dirname, '../fixtures/android_project');
describe('AndroidProject class', function() { describe('AndroidProject class', function () {
describe('getPackageName method', function() { describe('getPackageName method', function () {
it('Test#001 : should return an android project\'s proper package name', function() { it('Test#001 : should return an android project\'s proper package name', function () {
expect(AndroidProject.getProjectFile(android_project).getPackageName()) expect(AndroidProject.getProjectFile(android_project).getPackageName())
.toEqual('com.alunny.childapp'); .toEqual('com.alunny.childapp');
}); });

View File

@ -2,16 +2,15 @@
var path = require('path'); var path = require('path');
var AndroidStudio = require('../../bin/templates/cordova/lib/AndroidStudio'); var AndroidStudio = require('../../bin/templates/cordova/lib/AndroidStudio');
describe('AndroidStudio module', function () { describe('AndroidStudio module', function () {
it('should return true for Android Studio project', function() { it('should return true for Android Studio project', function () {
var root = path.join(__dirname,'../fixtures/android_studio_project/'); var root = path.join(__dirname, '../fixtures/android_studio_project/');
var isAndStud = AndroidStudio.isAndroidStudioProject(root); var isAndStud = AndroidStudio.isAndroidStudioProject(root);
expect(isAndStud).toBe(true); expect(isAndStud).toBe(true);
}); });
it('should return false non Android Studio project', function() { it('should return false non Android Studio project', function () {
var root = path.join(__dirname,'../fixtures/android_project/'); var root = path.join(__dirname, '../fixtures/android_project/');
var isAndStud = AndroidStudio.isAndroidStudioProject(root); var isAndStud = AndroidStudio.isAndroidStudioProject(root);
expect(isAndStud).toBe(false); expect(isAndStud).toBe(false);
}); });
}); });

View File

@ -17,8 +17,6 @@
under the License. under the License.
*/ */
/* jshint node:true */
var Q = require('q'); var Q = require('q');
var os = require('os'); var os = require('os');
var path = require('path'); var path = require('path');
@ -37,7 +35,7 @@ describe('addPlugin method', function () {
var api, fail, gradleBuilder, oldClean; var api, fail, gradleBuilder, oldClean;
var Api = rewire('../../bin/templates/cordova/Api'); var Api = rewire('../../bin/templates/cordova/Api');
beforeEach(function() { beforeEach(function () {
var pluginManager = jasmine.createSpyObj('pluginManager', ['addPlugin']); var pluginManager = jasmine.createSpyObj('pluginManager', ['addPlugin']);
pluginManager.addPlugin.and.returnValue(Q()); pluginManager.addPlugin.and.returnValue(Q());
spyOn(common.PluginManager, 'get').and.returnValue(pluginManager); spyOn(common.PluginManager, 'get').and.returnValue(pluginManager);
@ -58,20 +56,16 @@ describe('addPlugin method', function () {
Api.__set__('Api.prototype.clean', oldClean); Api.__set__('Api.prototype.clean', oldClean);
}); });
it('Test#001 : should call gradleBuilder.prepBuildFiles for every plugin with frameworks', function(done) { it('Test#001 : should call gradleBuilder.prepBuildFiles for every plugin with frameworks', function (done) {
api.addPlugin(new PluginInfo(path.join(FIXTURES, 'cordova-plugin-fake'))) api.addPlugin(new PluginInfo(path.join(FIXTURES, 'cordova-plugin-fake'))).catch(fail).fin(function () {
.catch(fail)
.fin(function () {
expect(fail).not.toHaveBeenCalled(); expect(fail).not.toHaveBeenCalled();
expect(gradleBuilder.prepBuildFiles).toHaveBeenCalled(); expect(gradleBuilder.prepBuildFiles).toHaveBeenCalled();
done(); done();
}); });
}); });
it('Test#002 : shouldn\'t trigger gradleBuilder.prepBuildFiles for plugins without android frameworks', function(done) { it('Test#002 : shouldn\'t trigger gradleBuilder.prepBuildFiles for plugins without android frameworks', function (done) {
api.addPlugin(new PluginInfo(path.join(FIXTURES, 'cordova-plugin-fake-ios-frameworks'))) api.addPlugin(new PluginInfo(path.join(FIXTURES, 'cordova-plugin-fake-ios-frameworks'))).catch(fail).fin(function () {
.catch(fail)
.fin(function () {
expect(fail).not.toHaveBeenCalled(); expect(fail).not.toHaveBeenCalled();
expect(gradleBuilder.prepBuildFiles).not.toHaveBeenCalled(); expect(gradleBuilder.prepBuildFiles).not.toHaveBeenCalled();
done(); done();

View File

@ -16,115 +16,107 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* jshint laxcomma:true */
var android_sdk = require("../../bin/templates/cordova/lib/android_sdk"); var android_sdk = require('../../bin/templates/cordova/lib/android_sdk');
var superspawn = require("cordova-common").superspawn; var superspawn = require('cordova-common').superspawn;
var fs = require("fs"); var fs = require('fs');
var path = require("path"); var path = require('path');
var Q = require("q"); var Q = require('q');
describe("android_sdk", function () { describe('android_sdk', function () {
describe("list_targets_with_android", function() { describe('list_targets_with_android', function () {
it("should invoke `android` with the `list target` command and _not_ the `list targets` command, as the plural form is not supported in some Android SDK Tools versions", function() { it('should invoke `android` with the `list target` command and _not_ the `list targets` command, as the plural form is not supported in some Android SDK Tools versions', function () {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(superspawn, "spawn").and.returnValue(deferred.promise); spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
android_sdk.list_targets_with_android(); android_sdk.list_targets_with_android();
expect(superspawn.spawn).toHaveBeenCalledWith("android", ["list", "target"]); expect(superspawn.spawn).toHaveBeenCalledWith('android', ['list', 'target']);
}); });
it("should parse and return results from `android list targets` command", function(done) { it('should parse and return results from `android list targets` command', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(superspawn, "spawn").and.returnValue(deferred.promise); spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
deferred.resolve(fs.readFileSync(path.join("spec", "fixtures", "sdk25.2-android_list_targets.txt"), "utf-8")); deferred.resolve(fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_targets.txt'), 'utf-8'));
return android_sdk.list_targets_with_android() return android_sdk.list_targets_with_android().then(function (list) {
.then(function(list) { [ 'Google Inc.:Google APIs:23',
[ "Google Inc.:Google APIs:23", 'Google Inc.:Google APIs:22',
"Google Inc.:Google APIs:22", 'Google Inc.:Google APIs:21',
"Google Inc.:Google APIs:21", 'android-25',
"android-25", 'android-24',
"android-24", 'android-N',
"android-N", 'android-23',
"android-23", 'android-MNC',
"android-MNC", 'android-22',
"android-22", 'android-21',
"android-21", 'android-20' ].forEach(function (target) { expect(list).toContain(target); });
"android-20" ].forEach(function(target) { }).fail(function (err) {
expect(list).toContain(target);
});
}).fail(function(err) {
console.log(err); console.log(err);
expect(err).toBeUndefined(); expect(err).toBeUndefined();
}).fin(function() { }).fin(function () {
done(); done();
}); });
}); });
}); });
describe("list_targets_with_avdmanager", function() { describe('list_targets_with_avdmanager', function () {
it("should parse and return results from `avdmanager list target` command", function(done) { it('should parse and return results from `avdmanager list target` command', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(superspawn, "spawn").and.returnValue(deferred.promise); spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
deferred.resolve(fs.readFileSync(path.join("spec", "fixtures", "sdk25.3-avdmanager_list_target.txt"), "utf-8")); deferred.resolve(fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_target.txt'), 'utf-8'));
return android_sdk.list_targets_with_avdmanager() return android_sdk.list_targets_with_avdmanager().then(function (list) {
.then(function(list) { expect(list).toContain('android-25');
expect(list).toContain("android-25"); }).fail(function (err) {
}).fail(function(err) {
console.log(err); console.log(err);
expect(err).toBeUndefined(); expect(err).toBeUndefined();
}).fin(function() { }).fin(function () {
done(); done();
}); });
}); });
}); });
describe("list_targets", function() { describe('list_targets', function () {
it("should parse Android SDK installed target information with `avdmanager` command first", function() { it('should parse Android SDK installed target information with `avdmanager` command first', function () {
var deferred = Q.defer(); var deferred = Q.defer();
var avdmanager_spy = spyOn(android_sdk, "list_targets_with_avdmanager").and.returnValue(deferred.promise); var avdmanager_spy = spyOn(android_sdk, 'list_targets_with_avdmanager').and.returnValue(deferred.promise);
android_sdk.list_targets(); android_sdk.list_targets();
expect(avdmanager_spy).toHaveBeenCalled(); expect(avdmanager_spy).toHaveBeenCalled();
}); });
it("should parse Android SDK installed target information with `android` command if list_targets_with_avdmanager fails with ENOENT", function(done) { it('should parse Android SDK installed target information with `android` command if list_targets_with_avdmanager fails with ENOENT', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(android_sdk, "list_targets_with_avdmanager").and.returnValue(deferred.promise); spyOn(android_sdk, 'list_targets_with_avdmanager').and.returnValue(deferred.promise);
deferred.reject({ deferred.reject({
code: "ENOENT" code: 'ENOENT'
}); });
var twoferred = Q.defer(); var twoferred = Q.defer();
twoferred.resolve(["target1"]); twoferred.resolve(['target1']);
var avdmanager_spy = spyOn(android_sdk, "list_targets_with_android").and.returnValue(twoferred.promise); var avdmanager_spy = spyOn(android_sdk, 'list_targets_with_android').and.returnValue(twoferred.promise);
return android_sdk.list_targets() return android_sdk.list_targets().then(function (targets) {
.then(function(targets) {
expect(avdmanager_spy).toHaveBeenCalled(); expect(avdmanager_spy).toHaveBeenCalled();
expect(targets[0]).toEqual("target1"); expect(targets[0]).toEqual('target1');
done(); done();
}); });
}); });
it("should parse Android SDK installed target information with `android` command if list_targets_with_avdmanager fails with not-recognized error (Windows)", function(done) { it('should parse Android SDK installed target information with `android` command if list_targets_with_avdmanager fails with not-recognized error (Windows)', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(android_sdk, "list_targets_with_avdmanager").and.returnValue(deferred.promise); spyOn(android_sdk, 'list_targets_with_avdmanager').and.returnValue(deferred.promise);
deferred.reject({ deferred.reject({
code: 1, code: 1,
stderr: "'avdmanager' is not recognized as an internal or external commmand,\r\noperable program or batch file.\r\n" stderr: "'avdmanager' is not recognized as an internal or external commmand,\r\noperable program or batch file.\r\n"
}); });
var twoferred = Q.defer(); var twoferred = Q.defer();
twoferred.resolve(["target1"]); twoferred.resolve(['target1']);
var avdmanager_spy = spyOn(android_sdk, "list_targets_with_android").and.returnValue(twoferred.promise); var avdmanager_spy = spyOn(android_sdk, 'list_targets_with_android').and.returnValue(twoferred.promise);
return android_sdk.list_targets() return android_sdk.list_targets().then(function (targets) {
.then(function(targets) {
expect(avdmanager_spy).toHaveBeenCalled(); expect(avdmanager_spy).toHaveBeenCalled();
expect(targets[0]).toEqual("target1"); expect(targets[0]).toEqual('target1');
done(); done();
}); });
}); });
it("should throw an error if no Android targets were found.", function(done) { it('should throw an error if no Android targets were found.', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(android_sdk, "list_targets_with_avdmanager").and.returnValue(deferred.promise); spyOn(android_sdk, 'list_targets_with_avdmanager').and.returnValue(deferred.promise);
deferred.resolve([]); deferred.resolve([]);
return android_sdk.list_targets() return android_sdk.list_targets().then(function (targets) {
.then(function(targets) {
done.fail(); done.fail();
}).catch(function(err) { }).catch(function (err) {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(err.message).toContain("No android targets (SDKs) installed!"); expect(err.message).toContain('No android targets (SDKs) installed!');
done(); done();
}); });
}); });

View File

@ -1,22 +1,23 @@
var gradle_builder = require('../../../bin/templates/cordova/lib/builders/GradleBuilder.js');
var Gradle_builder = require('../../../bin/templates/cordova/lib/builders/GradleBuilder.js');
var fs = require('fs'); var fs = require('fs');
var superspawn = require('cordova-common').superspawn; var superspawn = require('cordova-common').superspawn;
var builder; var builder;
describe('Gradle Builder', function() { describe('Gradle Builder', function () {
beforeEach(function() { beforeEach(function () {
spyOn(fs, 'existsSync').and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
builder = new gradle_builder('/root'); builder = new Gradle_builder('/root');
spyOn(superspawn, 'spawn'); spyOn(superspawn, 'spawn');
}); });
describe('runGradleWrapper method', function() { describe('runGradleWrapper method', function () {
it('should run the provided gradle command if a gradle wrapper does not already exist', function() { it('should run the provided gradle command if a gradle wrapper does not already exist', function () {
fs.existsSync.and.returnValue(false); fs.existsSync.and.returnValue(false);
builder.runGradleWrapper('/my/sweet/gradle'); builder.runGradleWrapper('/my/sweet/gradle');
expect(superspawn.spawn).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object)); expect(superspawn.spawn).toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));
}); });
it('should do nothing if a gradle wrapper exists in the project directory', function() { it('should do nothing if a gradle wrapper exists in the project directory', function () {
fs.existsSync.and.returnValue(true); fs.existsSync.and.returnValue(true);
builder.runGradleWrapper('/my/sweet/gradle'); builder.runGradleWrapper('/my/sweet/gradle');
expect(superspawn.spawn).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object)); expect(superspawn.spawn).not.toHaveBeenCalledWith('/my/sweet/gradle', jasmine.any(Array), jasmine.any(Object));

View File

@ -16,234 +16,222 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* jshint laxcomma:true */
var check_reqs = require("../../bin/templates/cordova/lib/check_reqs"); var check_reqs = require('../../bin/templates/cordova/lib/check_reqs');
var android_sdk = require("../../bin/templates/cordova/lib/android_sdk"); var android_sdk = require('../../bin/templates/cordova/lib/android_sdk');
var shelljs = require("shelljs"); var shelljs = require('shelljs');
var fs = require("fs"); var fs = require('fs');
var path = require("path"); var path = require('path');
var Q = require("q"); var Q = require('q');
describe("check_reqs", function () { describe('check_reqs', function () {
var original_env; var original_env;
beforeAll(function() { beforeAll(function () {
original_env = Object.create(process.env); original_env = Object.create(process.env);
}); });
afterEach(function() { afterEach(function () {
Object.keys(original_env).forEach(function(k) { Object.keys(original_env).forEach(function (k) {
process.env[k] = original_env[k]; process.env[k] = original_env[k];
}); });
}); });
describe("check_android", function() { describe('check_android', function () {
describe("set ANDROID_HOME if not set", function() { describe('set ANDROID_HOME if not set', function () {
beforeEach(function() { beforeEach(function () {
delete process.env.ANDROID_HOME; delete process.env.ANDROID_HOME;
}); });
describe("even if no Android binaries are on the PATH", function() { describe('even if no Android binaries are on the PATH', function () {
beforeEach(function() { beforeEach(function () {
spyOn(shelljs, "which").and.returnValue(null); spyOn(shelljs, 'which').and.returnValue(null);
spyOn(fs, "existsSync").and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
}); });
it("it should set ANDROID_HOME on Windows", function(done) { it('it should set ANDROID_HOME on Windows', function (done) {
spyOn(check_reqs, "isWindows").and.returnValue(true); spyOn(check_reqs, 'isWindows').and.returnValue(true);
process.env.LOCALAPPDATA = "windows-local-app-data"; process.env.LOCALAPPDATA = 'windows-local-app-data';
process.env.ProgramFiles = "windows-program-files"; process.env.ProgramFiles = 'windows-program-files';
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() { expect(process.env.ANDROID_HOME).toContain('windows-local-app-data');
expect(process.env.ANDROID_HOME).toContain("windows-local-app-data"); }).fail(function (err) {
}).fail(function(err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
console.log(err); console.log(err);
}).fin(function() { }).fin(function () {
delete process.env.LOCALAPPDATA; delete process.env.LOCALAPPDATA;
delete process.env.ProgramFiles; delete process.env.ProgramFiles;
done(); done();
}); });
}); });
it("it should set ANDROID_HOME on Darwin", function(done) { it('it should set ANDROID_HOME on Darwin', function (done) {
spyOn(check_reqs, "isWindows").and.returnValue(false); spyOn(check_reqs, 'isWindows').and.returnValue(false);
spyOn(check_reqs, "isDarwin").and.returnValue(true); spyOn(check_reqs, 'isDarwin').and.returnValue(true);
process.env.HOME = "home is where the heart is"; process.env.HOME = 'home is where the heart is';
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() { expect(process.env.ANDROID_HOME).toContain('home is where the heart is');
expect(process.env.ANDROID_HOME).toContain("home is where the heart is"); }).fail(function (err) {
}).fail(function(err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
console.log(err); console.log(err);
}).fin(function() { }).fin(function () {
delete process.env.HOME; delete process.env.HOME;
done(); done();
}); });
}); });
}); });
describe("if some Android tooling exists on the PATH", function() { describe('if some Android tooling exists on the PATH', function () {
beforeEach(function() { beforeEach(function () {
spyOn(fs, "realpathSync").and.callFake(function(path) { spyOn(fs, 'realpathSync').and.callFake(function (path) {
return path; return path;
}); });
}); });
it("should set ANDROID_HOME based on `android` command if command exists in a SDK-like directory structure", function(done) { it('should set ANDROID_HOME based on `android` command if command exists in a SDK-like directory structure', function (done) {
spyOn(fs, "existsSync").and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "android") { if (cmd === 'android') {
return "/android/sdk/tools/android"; return '/android/sdk/tools/android';
} else { } else {
return null; return null;
} }
}); });
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() { expect(process.env.ANDROID_HOME).toEqual('/android/sdk');
expect(process.env.ANDROID_HOME).toEqual("/android/sdk");
done(); done();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
console.log(err); console.log(err);
}); });
}); });
it("should error out if `android` command exists in a non-SDK-like directory structure", function(done) { it('should error out if `android` command exists in a non-SDK-like directory structure', function (done) {
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "android") { if (cmd === 'android') {
return "/just/some/random/path/android"; return '/just/some/random/path/android';
} else { } else {
return null; return null;
} }
}); });
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() {
done.fail(); done.fail();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(err.message).toContain("update your PATH to include valid path"); expect(err.message).toContain('update your PATH to include valid path');
done(); done();
}); });
}); });
it("should set ANDROID_HOME based on `adb` command if command exists in a SDK-like directory structure", function(done) { it('should set ANDROID_HOME based on `adb` command if command exists in a SDK-like directory structure', function (done) {
spyOn(fs, "existsSync").and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "adb") { if (cmd === 'adb') {
return "/android/sdk/platform-tools/adb"; return '/android/sdk/platform-tools/adb';
} else { } else {
return null; return null;
} }
}); });
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() { expect(process.env.ANDROID_HOME).toEqual('/android/sdk');
expect(process.env.ANDROID_HOME).toEqual("/android/sdk");
done(); done();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
console.log(err); console.log(err);
}); });
}); });
it("should error out if `adb` command exists in a non-SDK-like directory structure", function(done) { it('should error out if `adb` command exists in a non-SDK-like directory structure', function (done) {
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "adb") { if (cmd === 'adb') {
return "/just/some/random/path/adb"; return '/just/some/random/path/adb';
} else { } else {
return null; return null;
} }
}); });
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() {
done.fail(); done.fail();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(err.message).toContain("update your PATH to include valid path"); expect(err.message).toContain('update your PATH to include valid path');
done(); done();
}); });
}); });
it("should set ANDROID_HOME based on `avdmanager` command if command exists in a SDK-like directory structure", function(done) { it('should set ANDROID_HOME based on `avdmanager` command if command exists in a SDK-like directory structure', function (done) {
spyOn(fs, "existsSync").and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "avdmanager") { if (cmd === 'avdmanager') {
return "/android/sdk/tools/bin/avdmanager"; return '/android/sdk/tools/bin/avdmanager';
} else { } else {
return null; return null;
} }
}); });
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() { expect(process.env.ANDROID_HOME).toEqual('/android/sdk');
expect(process.env.ANDROID_HOME).toEqual("/android/sdk");
done(); done();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
console.log(err); console.log(err);
}); });
}); });
it("should error out if `avdmanager` command exists in a non-SDK-like directory structure", function(done) { it('should error out if `avdmanager` command exists in a non-SDK-like directory structure', function (done) {
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "avdmanager") { if (cmd === 'avdmanager') {
return "/just/some/random/path/avdmanager"; return '/just/some/random/path/avdmanager';
} else { } else {
return null; return null;
} }
}); });
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() {
done.fail(); done.fail();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(err.message).toContain("update your PATH to include valid path"); expect(err.message).toContain('update your PATH to include valid path');
done(); done();
}); });
}); });
}); });
}); });
describe("set PATH for various Android binaries if not available", function() { describe('set PATH for various Android binaries if not available', function () {
beforeEach(function() { beforeEach(function () {
spyOn(shelljs, "which").and.returnValue(null); spyOn(shelljs, 'which').and.returnValue(null);
process.env.ANDROID_HOME = "let the children play"; process.env.ANDROID_HOME = 'let the children play';
spyOn(fs, "existsSync").and.returnValue(true); spyOn(fs, 'existsSync').and.returnValue(true);
}); });
afterEach(function() { afterEach(function () {
delete process.env.ANDROID_HOME; delete process.env.ANDROID_HOME;
}); });
it("should add tools/bin,tools,platform-tools to PATH if `avdmanager`,`android`,`adb` is not found", function(done) { it('should add tools/bin,tools,platform-tools to PATH if `avdmanager`,`android`,`adb` is not found', function (done) {
return check_reqs.check_android() return check_reqs.check_android().then(function () {
.then(function() { expect(process.env.PATH).toContain('let the children play' + path.sep + 'tools');
expect(process.env.PATH).toContain("let the children play" + path.sep + "tools"); expect(process.env.PATH).toContain('let the children play' + path.sep + 'platform-tools');
expect(process.env.PATH).toContain("let the children play" + path.sep + "platform-tools"); expect(process.env.PATH).toContain('let the children play' + path.sep + 'tools' + path.sep + 'bin');
expect(process.env.PATH).toContain("let the children play" + path.sep + "tools" + path.sep + "bin");
done(); done();
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
console.log(err); console.log(err);
}); });
}); });
}); });
}); });
describe("get_target", function() { describe('get_target', function () {
it("should retrieve target from framework project.properties file", function() { it('should retrieve target from framework project.properties file', function () {
var target = check_reqs.get_target(); var target = check_reqs.get_target();
expect(target).toBeDefined(); expect(target).toBeDefined();
expect(target).toContain("android-"); expect(target).toContain('android-');
}); });
}); });
describe("check_android_target", function() { describe('check_android_target', function () {
it("should should return full list of supported targets if there is a match to ideal api level", function(done) { it('should should return full list of supported targets if there is a match to ideal api level', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(android_sdk, "list_targets").and.returnValue(deferred.promise); spyOn(android_sdk, 'list_targets').and.returnValue(deferred.promise);
var fake_targets = ["you are my fire", "my one desire"]; var fake_targets = ['you are my fire', 'my one desire'];
deferred.resolve(fake_targets); deferred.resolve(fake_targets);
spyOn(check_reqs, "get_target").and.returnValue("you are my fire"); spyOn(check_reqs, 'get_target').and.returnValue('you are my fire');
return check_reqs.check_android_target() return check_reqs.check_android_target().then(function (targets) {
.then(function(targets) {
expect(targets).toBeDefined(); expect(targets).toBeDefined();
expect(targets).toEqual(fake_targets); expect(targets).toEqual(fake_targets);
done(); done();
}); });
}); });
it("should error out if there is no match between ideal api level and installed targets", function(done) { it('should error out if there is no match between ideal api level and installed targets', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(android_sdk, "list_targets").and.returnValue(deferred.promise); spyOn(android_sdk, 'list_targets').and.returnValue(deferred.promise);
var fake_targets = ["you are my fire", "my one desire"]; var fake_targets = ['you are my fire', 'my one desire'];
deferred.resolve(fake_targets); deferred.resolve(fake_targets);
spyOn(check_reqs, "get_target").and.returnValue("and i knowwwwwwwwwwww"); spyOn(check_reqs, 'get_target').and.returnValue('and i knowwwwwwwwwwww');
return check_reqs.check_android_target() return check_reqs.check_android_target().catch(function (err) {
.catch(function(err) {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(err.message).toContain("Please install Android target"); expect(err.message).toContain('Please install Android target');
done(); done();
}); });
}); });

View File

@ -16,97 +16,91 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* jshint laxcomma:true */
var create = require("../../bin/lib/create"); var create = require('../../bin/lib/create');
describe("create", function () { describe('create', function () {
describe("validatePackageName", function() { describe('validatePackageName', function () {
var valid = [ var valid = [
"org.apache.mobilespec" 'org.apache.mobilespec',
, "com.example" 'com.example',
, "com.floors42.package" 'com.floors42.package',
, "ball8.ball8.ball8ball" 'ball8.ball8.ball8ball'
]; ];
var invalid = [ var invalid = [
"" '',
, "com.class.is.bad" 'com.class.is.bad',
, "0com.example.mobilespec" '0com.example.mobilespec',
, "c-m.e@a!p%e.mobilespec" 'c-m.e@a!p%e.mobilespec',
, "notenoughdots" 'notenoughdots',
, ".starts.with.a.dot" '.starts.with.a.dot',
, "ends.with.a.dot." 'ends.with.a.dot.',
, "_underscore.anything" '_underscore.anything',
, "underscore._something" 'underscore._something',
, "_underscore._all._the._things" '_underscore._all._the._things',
, "8.ball" '8.ball',
, "8ball.ball" '8ball.ball',
, "ball8.8ball" 'ball8.8ball',
, "ball8.com.8ball" 'ball8.com.8ball'
]; ];
valid.forEach(function(package_name) { valid.forEach(function (package_name) {
it("Test#001 : should accept " + package_name, function(done) { it('Test#001 : should accept ' + package_name, function (done) {
return create.validatePackageName(package_name) return create.validatePackageName(package_name).then(function () {
.then(function() { // resolved
//resolved done();
done(); }).fail(function (err) {
}).fail(function(err) { expect(err).toBeUndefined();
expect(err).toBeUndefined(); });
});
}); });
});
});
invalid.forEach(function(package_name) { invalid.forEach(function (package_name) {
it("Test#002 : should reject " + package_name, function(done) { it('Test#002 : should reject ' + package_name, function (done) {
return create.validatePackageName(package_name) return create.validatePackageName(package_name).then(function () {
.then(function() { // shouldn't be here
//shouldn't be here expect(true).toBe(false);
expect(true).toBe(false); }).fail(function (err) {
}).fail(function(err) { expect(err).toBeDefined();
expect(err).toBeDefined(); done();
done(); });
});
}); });
});
}); });
}); describe('validateProjectName', function () {
describe("validateProjectName", function() { var valid = [
var valid = [ 'mobilespec',
"mobilespec" 'package_name',
, "package_name" 'PackageName',
, "PackageName" 'CordovaLib'
, "CordovaLib" ];
]; var invalid = [
var invalid = [ '',
"" '0startswithdigit',
, "0startswithdigit" 'CordovaActivity'
, "CordovaActivity" ];
];
valid.forEach(function(project_name) { valid.forEach(function (project_name) {
it("Test#003 : should accept " + project_name, function(done) { it('Test#003 : should accept ' + project_name, function (done) {
return create.validateProjectName(project_name) return create.validateProjectName(project_name).then(function () {
.then(function() { // resolved
//resolved done();
done(); }).fail(function (err) {
}).fail(function(err) { expect(err).toBeUndefined();
expect(err).toBeUndefined(); });
});
}); });
});
});
invalid.forEach(function(project_name) { invalid.forEach(function (project_name) {
it("Test#004 : should reject " + project_name, function(done) { it('Test#004 : should reject ' + project_name, function (done) {
return create.validateProjectName(project_name) return create.validateProjectName(project_name).then(function () {
.then(function() { // shouldn't be here
//shouldn't be here expect(true).toBe(false);
expect(true).toBe(false); }).fail(function (err) {
}).fail(function(err) { expect(err).toBeDefined();
expect(err).toBeDefined(); done();
done(); });
});
}); });
});
}); });
});
}); });

View File

@ -16,96 +16,94 @@
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var emu = require("../../bin/templates/cordova/lib/emulator");
var superspawn = require("cordova-common").superspawn;
var Q = require("q");
var fs = require("fs");
var path = require("path");
var shelljs = require("shelljs");
describe("emulator", function () { var emu = require('../../bin/templates/cordova/lib/emulator');
describe("list_images_using_avdmanager", function() { var superspawn = require('cordova-common').superspawn;
it("should properly parse details of SDK Tools 25.3.1 `avdmanager` output", function(done) { var Q = require('q');
var fs = require('fs');
var path = require('path');
var shelljs = require('shelljs');
describe('emulator', function () {
describe('list_images_using_avdmanager', function () {
it('should properly parse details of SDK Tools 25.3.1 `avdmanager` output', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(superspawn, "spawn").and.returnValue(deferred.promise); spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
deferred.resolve(fs.readFileSync(path.join("spec", "fixtures", "sdk25.3-avdmanager_list_avd.txt"), "utf-8")); deferred.resolve(fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.3-avdmanager_list_avd.txt'), 'utf-8'));
return emu.list_images_using_avdmanager() return emu.list_images_using_avdmanager().then(function (list) {
.then(function(list) {
expect(list).toBeDefined(); expect(list).toBeDefined();
expect(list[0].name).toEqual("nexus5-5.1"); expect(list[0].name).toEqual('nexus5-5.1');
expect(list[0].target).toEqual("Android 5.1 (API level 22)"); expect(list[0].target).toEqual('Android 5.1 (API level 22)');
expect(list[1].device).toEqual("pixel (Google)"); expect(list[1].device).toEqual('pixel (Google)');
expect(list[2].abi).toEqual("default/x86_64"); expect(list[2].abi).toEqual('default/x86_64');
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
}).fin(function() { }).fin(function () {
done(); done();
}); });
}); });
}); });
describe("list_images_using_android", function() { describe('list_images_using_android', function () {
it("should invoke `android` with the `list avd` command and _not_ the `list avds` command, as the plural form is not supported in some Android SDK Tools versions", function() { it('should invoke `android` with the `list avd` command and _not_ the `list avds` command, as the plural form is not supported in some Android SDK Tools versions', function () {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(superspawn, "spawn").and.returnValue(deferred.promise); spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
emu.list_images_using_android(); emu.list_images_using_android();
expect(superspawn.spawn).toHaveBeenCalledWith("android", ["list", "avd"]); expect(superspawn.spawn).toHaveBeenCalledWith('android', ['list', 'avd']);
}); });
it("should properly parse details of SDK Tools pre-25.3.1 `android list avd` output", function(done) { it('should properly parse details of SDK Tools pre-25.3.1 `android list avd` output', function (done) {
var deferred = Q.defer(); var deferred = Q.defer();
spyOn(superspawn, "spawn").and.returnValue(deferred.promise); spyOn(superspawn, 'spawn').and.returnValue(deferred.promise);
deferred.resolve(fs.readFileSync(path.join("spec", "fixtures", "sdk25.2-android_list_avd.txt"), "utf-8")); deferred.resolve(fs.readFileSync(path.join('spec', 'fixtures', 'sdk25.2-android_list_avd.txt'), 'utf-8'));
return emu.list_images_using_android() return emu.list_images_using_android().then(function (list) {
.then(function(list) {
expect(list).toBeDefined(); expect(list).toBeDefined();
expect(list[0].name).toEqual("QWR"); expect(list[0].name).toEqual('QWR');
expect(list[0].device).toEqual("Nexus 5 (Google)"); expect(list[0].device).toEqual('Nexus 5 (Google)');
expect(list[0].path).toEqual("/Users/shazron/.android/avd/QWR.avd"); expect(list[0].path).toEqual('/Users/shazron/.android/avd/QWR.avd');
expect(list[0].target).toEqual("Android 7.1.1 (API level 25)"); expect(list[0].target).toEqual('Android 7.1.1 (API level 25)');
expect(list[0].abi).toEqual("google_apis/x86_64"); expect(list[0].abi).toEqual('google_apis/x86_64');
expect(list[0].skin).toEqual("1080x1920"); expect(list[0].skin).toEqual('1080x1920');
}).fail(function(err) { }).fail(function (err) {
expect(err).toBeUndefined(); expect(err).toBeUndefined();
}).fin(function() { }).fin(function () {
done(); done();
}); });
}); });
}); });
describe("list_images", function() { describe('list_images', function () {
beforeEach(function() { beforeEach(function () {
spyOn(fs, "realpathSync").and.callFake(function(cmd) { spyOn(fs, 'realpathSync').and.callFake(function (cmd) {
return cmd; return cmd;
}); });
}); });
it("should try to parse AVD information using `avdmanager` first", function() { it('should try to parse AVD information using `avdmanager` first', function () {
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "avdmanager") { if (cmd === 'avdmanager') {
return true; return true;
} else { } else {
return false; return false;
} }
}); });
var avdmanager_spy = spyOn(emu, "list_images_using_avdmanager").and.returnValue({catch:function(){}}); var avdmanager_spy = spyOn(emu, 'list_images_using_avdmanager').and.returnValue({catch: function () {}});
emu.list_images(); emu.list_images();
expect(avdmanager_spy).toHaveBeenCalled(); expect(avdmanager_spy).toHaveBeenCalled();
}); });
it("should delegate to `android` if `avdmanager` cant be found and `android` can", function() { it('should delegate to `android` if `avdmanager` cant be found and `android` can', function () {
spyOn(shelljs, "which").and.callFake(function(cmd) { spyOn(shelljs, 'which').and.callFake(function (cmd) {
if (cmd == "avdmanager") { if (cmd === 'avdmanager') {
return false; return false;
} else { } else {
return true; return true;
} }
}); });
var android_spy = spyOn(emu, "list_images_using_android"); var android_spy = spyOn(emu, 'list_images_using_android');
emu.list_images(); emu.list_images();
expect(android_spy).toHaveBeenCalled(); expect(android_spy).toHaveBeenCalled();
}); });
it("should throw an error if neither `avdmanager` nor `android` are able to be found", function(done) { it('should throw an error if neither `avdmanager` nor `android` are able to be found', function (done) {
spyOn(shelljs, "which").and.returnValue(false); spyOn(shelljs, 'which').and.returnValue(false);
return emu.list_images() return emu.list_images().catch(function (err) {
.catch(function(err) {
expect(err).toBeDefined(); expect(err).toBeDefined();
expect(err.message).toContain("Could not find either `android` or `avdmanager`"); expect(err.message).toContain('Could not find either `android` or `avdmanager`');
done(); done();
}); });
}); });

View File

@ -35,25 +35,25 @@ var copyFile = common.__get__('copyFile');
var deleteJava = common.__get__('deleteJava'); var deleteJava = common.__get__('deleteJava');
var copyNewFile = common.__get__('copyNewFile'); var copyNewFile = common.__get__('copyNewFile');
describe('common platform handler', function() { describe('common platform handler', function () {
describe('copyFile', function() { describe('copyFile', function () {
it('Test#001 : should throw if source path not found', function(){ it('Test#001 : should throw if source path not found', function () {
shell.rm('-rf', src); shell.rm('-rf', src);
expect(function(){copyFile(test_dir, src, project_dir, dest);}) expect(function () { copyFile(test_dir, src, project_dir, dest); })
.toThrow(new Error('"' + src + '" not found!')); .toThrow(new Error('"' + src + '" not found!'));
}); });
it('Test#002 : should throw if src not in plugin directory', function(){ it('Test#002 : should throw if src not in plugin directory', function () {
shell.mkdir('-p', project_dir); shell.mkdir('-p', project_dir);
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8'); fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
var outside_file = '../non_plugin_file'; var outside_file = '../non_plugin_file';
expect(function(){copyFile(test_dir, outside_file, project_dir, dest);}). expect(function () { copyFile(test_dir, outside_file, project_dir, dest); })
toThrow(new Error('File "' + path.resolve(test_dir, outside_file) + '" is located outside the plugin directory "' + test_dir + '"')); .toThrow(new Error('File "' + path.resolve(test_dir, outside_file) + '" is located outside the plugin directory "' + test_dir + '"'));
shell.rm('-rf', test_dir); shell.rm('-rf', test_dir);
}); });
it('Test#003 : should allow symlink src, if inside plugin', function(){ it('Test#003 : should allow symlink src, if inside plugin', function () {
shell.mkdir('-p', java_dir); shell.mkdir('-p', java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8'); fs.writeFileSync(java_file, 'contents', 'utf-8');
@ -66,7 +66,7 @@ describe('common platform handler', function() {
shell.rm('-rf', project_dir); shell.rm('-rf', project_dir);
}); });
it('Test#004 : should throw if symlink is linked to a file outside the plugin', function(){ it('Test#004 : should throw if symlink is linked to a file outside the plugin', function () {
shell.mkdir('-p', java_dir); shell.mkdir('-p', java_dir);
fs.writeFileSync(non_plugin_file, 'contents', 'utf-8'); fs.writeFileSync(non_plugin_file, 'contents', 'utf-8');
@ -75,20 +75,20 @@ describe('common platform handler', function() {
return; return;
} }
expect(function(){copyFile(test_dir, symlink_file, project_dir, dest);}). expect(function () { copyFile(test_dir, symlink_file, project_dir, dest); })
toThrow(new Error('File "' + path.resolve(test_dir, symlink_file) + '" is located outside the plugin directory "' + test_dir + '"')); .toThrow(new Error('File "' + path.resolve(test_dir, symlink_file) + '" is located outside the plugin directory "' + test_dir + '"'));
shell.rm('-rf', project_dir); shell.rm('-rf', project_dir);
}); });
it('Test#005 : should throw if dest is outside the project directory', function(){ it('Test#005 : should throw if dest is outside the project directory', function () {
shell.mkdir('-p', java_dir); shell.mkdir('-p', java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8'); fs.writeFileSync(java_file, 'contents', 'utf-8');
expect(function(){copyFile(test_dir, java_file, project_dir, non_plugin_file);}). expect(function () { copyFile(test_dir, java_file, project_dir, non_plugin_file); })
toThrow(new Error('Destination "' + path.resolve(project_dir, non_plugin_file) + '" for source file "' + path.resolve(test_dir, java_file) + '" is located outside the project')); .toThrow(new Error('Destination "' + path.resolve(project_dir, non_plugin_file) + '" for source file "' + path.resolve(test_dir, java_file) + '" is located outside the project'));
shell.rm('-rf', project_dir); shell.rm('-rf', project_dir);
}); });
it('Test#006 : should call mkdir -p on target path', function(){ it('Test#006 : should call mkdir -p on target path', function () {
shell.mkdir('-p', java_dir); shell.mkdir('-p', java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8'); fs.writeFileSync(java_file, 'contents', 'utf-8');
@ -102,7 +102,7 @@ describe('common platform handler', function() {
shell.rm('-rf', project_dir); shell.rm('-rf', project_dir);
}); });
it('Test#007 : should call cp source/dest paths', function(){ it('Test#007 : should call cp source/dest paths', function () {
shell.mkdir('-p', java_dir); shell.mkdir('-p', java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8'); fs.writeFileSync(java_file, 'contents', 'utf-8');
@ -119,46 +119,46 @@ describe('common platform handler', function() {
}); });
describe('copyNewFile', function () { describe('copyNewFile', function () {
it('Test#008 : should throw if target path exists', function(){ it('Test#008 : should throw if target path exists', function () {
shell.mkdir('-p', dest); shell.mkdir('-p', dest);
expect(function(){copyNewFile(test_dir, src, project_dir, dest);}). expect(function () { copyNewFile(test_dir, src, project_dir, dest); })
toThrow(new Error('"' + dest + '" already exists!')); .toThrow(new Error('"' + dest + '" already exists!'));
shell.rm('-rf', dest); shell.rm('-rf', dest);
}); });
}); });
describe('deleteJava', function() { describe('deleteJava', function () {
beforeEach(function() { beforeEach(function () {
shell.mkdir('-p', java_dir); shell.mkdir('-p', java_dir);
fs.writeFileSync(java_file, 'contents', 'utf-8'); fs.writeFileSync(java_file, 'contents', 'utf-8');
}); });
afterEach(function() { afterEach(function () {
shell.rm('-rf', java_dir); shell.rm('-rf', java_dir);
}); });
it('Test#009 : should call fs.unlinkSync on the provided paths', function(){ it('Test#009 : should call fs.unlinkSync on the provided paths', function () {
var s = spyOn(fs, 'unlinkSync').and.callThrough(); var s = spyOn(fs, 'unlinkSync').and.callThrough();
deleteJava(project_dir, java_file); deleteJava(project_dir, java_file);
expect(s).toHaveBeenCalled(); expect(s).toHaveBeenCalled();
expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file)); expect(s).toHaveBeenCalledWith(path.resolve(project_dir, java_file));
}); });
it('Test#010 : should delete empty directories after removing source code in a java src path hierarchy', function(){ it('Test#010 : should delete empty directories after removing source code in a java src path hierarchy', function () {
deleteJava(project_dir, java_file); deleteJava(project_dir, java_file);
expect(fs.existsSync(java_file)).not.toBe(true); expect(fs.existsSync(java_file)).not.toBe(true);
expect(fs.existsSync(java_dir)).not.toBe(true); expect(fs.existsSync(java_dir)).not.toBe(true);
expect(fs.existsSync(path.join(src,'one'))).not.toBe(true); expect(fs.existsSync(path.join(src, 'one'))).not.toBe(true);
}); });
it('Test#011 : should never delete the top-level src directory, even if all plugins added were removed', function(){ it('Test#011 : should never delete the top-level src directory, even if all plugins added were removed', function () {
deleteJava(project_dir, java_file); deleteJava(project_dir, java_file);
expect(fs.existsSync(src)).toBe(true); expect(fs.existsSync(src)).toBe(true);
}); });
}); });
}); });
function ignoreEPERMonWin32(symlink_src, symlink_dest) { function ignoreEPERMonWin32 (symlink_src, symlink_dest) {
try { try {
fs.symlinkSync(symlink_src, symlink_dest); fs.symlinkSync(symlink_src, symlink_dest);
} catch (e) { } catch (e) {

View File

@ -18,13 +18,13 @@
*/ */
var rewire = require('rewire'); var rewire = require('rewire');
var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers'); var common = rewire('../../../bin/templates/cordova/lib/pluginHandlers');
var android = common.__get__('handlers'); var android = common.__get__('handlers');
var path = require('path'); var path = require('path');
var fs = require('fs'); var fs = require('fs');
var shell = require('shelljs'); var shell = require('shelljs');
var os = require('os'); var os = require('os');
var temp = path.join(os.tmpdir(), 'plugman'); var temp = path.join(os.tmpdir(), 'plugman');
var plugins_dir = path.join(temp, 'cordova/plugins'); var plugins_dir = path.join(temp, 'cordova/plugins');
var dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin'); var dummyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.dummyplugin');
var faultyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.faultyplugin'); var faultyplugin = path.join(__dirname, '../../fixtures/org.test.plugins.faultyplugin');
@ -34,32 +34,32 @@ var PluginInfo = require('cordova-common').PluginInfo;
var AndroidProject = require('../../../bin/templates/cordova/lib/AndroidProject'); var AndroidProject = require('../../../bin/templates/cordova/lib/AndroidProject');
var dummyPluginInfo = new PluginInfo(dummyplugin); var dummyPluginInfo = new PluginInfo(dummyplugin);
var valid_source = dummyPluginInfo.getSourceFiles('android'), var valid_source = dummyPluginInfo.getSourceFiles('android');
valid_resources = dummyPluginInfo.getResourceFiles('android'), var valid_resources = dummyPluginInfo.getResourceFiles('android');
valid_libs = dummyPluginInfo.getLibFiles('android'); var valid_libs = dummyPluginInfo.getLibFiles('android');
var faultyPluginInfo = new PluginInfo(faultyplugin); var faultyPluginInfo = new PluginInfo(faultyplugin);
var invalid_source = faultyPluginInfo.getSourceFiles('android'); var invalid_source = faultyPluginInfo.getSourceFiles('android');
describe('android project handler', function() { describe('android project handler', function () {
describe('installation', function() { describe('installation', function () {
var copyFileOrig = common.__get__('copyFile'); var copyFileOrig = common.__get__('copyFile');
var copyFileSpy = jasmine.createSpy('copyFile'); var copyFileSpy = jasmine.createSpy('copyFile');
var dummyProject; var dummyProject;
beforeEach(function() { beforeEach(function () {
shell.mkdir('-p', temp); shell.mkdir('-p', temp);
dummyProject = AndroidProject.getProjectFile(temp); dummyProject = AndroidProject.getProjectFile(temp);
copyFileSpy.calls.reset(); copyFileSpy.calls.reset();
common.__set__('copyFile', copyFileSpy); common.__set__('copyFile', copyFileSpy);
}); });
afterEach(function() { afterEach(function () {
shell.rm('-rf', temp); shell.rm('-rf', temp);
common.__set__('copyFile', copyFileOrig); common.__set__('copyFile', copyFileOrig);
}); });
describe('of <lib-file> elements', function() { describe('of <lib-file> elements', function () {
it('Test#001 : should copy files', function () { it('Test#001 : should copy files', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject); android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin, 'src/android/TestLib.jar', temp, path.join('libs', 'TestLib.jar'), false); expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin, 'src/android/TestLib.jar', temp, path.join('libs', 'TestLib.jar'), false);
@ -70,58 +70,58 @@ describe('android project handler', function() {
}); });
}); });
describe('of <resource-file> elements', function() { describe('of <resource-file> elements', function () {
it('Test#003 : should copy files', function () { it('Test#003 : should copy files', function () {
android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject); android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin, 'android-resource.xml', temp, path.join('res', 'xml', 'dummy.xml'), false); expect(copyFileSpy).toHaveBeenCalledWith(dummyplugin, 'android-resource.xml', temp, path.join('res', 'xml', 'dummy.xml'), false);
}); });
}); });
describe('of <source-file> elements', function() { describe('of <source-file> elements', function () {
beforeEach(function() { beforeEach(function () {
shell.cp('-rf', android_project, temp); shell.cp('-rf', android_project, temp);
}); });
it('Test#004 : should copy stuff from one location to another by calling common.copyFile', function() { it('Test#004 : should copy stuff from one location to another by calling common.copyFile', function () {
android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject); android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject);
expect(copyFileSpy) expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin.java', temp, path.join('src/com/phonegap/plugins/dummyplugin/DummyPlugin.java'), false); .toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin.java', temp, path.join('src/com/phonegap/plugins/dummyplugin/DummyPlugin.java'), false);
}); });
it('Test#005 : should install source files to the right location for Android Studio projects', function() { it('Test#005 : should install source files to the right location for Android Studio projects', function () {
android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject, {android_studio: true}); android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject, {android_studio: true});
expect(copyFileSpy) expect(copyFileSpy)
.toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin.java', temp, path.join('app/src/main/java/com/phonegap/plugins/dummyplugin/DummyPlugin.java'), false); .toHaveBeenCalledWith(dummyplugin, 'src/android/DummyPlugin.java', temp, path.join('app/src/main/java/com/phonegap/plugins/dummyplugin/DummyPlugin.java'), false);
}); });
it('Test#006 : should throw if source file cannot be found', function() { it('Test#006 : should throw if source file cannot be found', function () {
common.__set__('copyFile', copyFileOrig); common.__set__('copyFile', copyFileOrig);
expect(function() { expect(function () {
android['source-file'].install(invalid_source[0], faultyPluginInfo, dummyProject); android['source-file'].install(invalid_source[0], faultyPluginInfo, dummyProject);
}).toThrow(new Error('"' + path.resolve(faultyplugin, 'src/android/NotHere.java') + '" not found!')); }).toThrow(new Error('"' + path.resolve(faultyplugin, 'src/android/NotHere.java') + '" not found!'));
}); });
it('Test#007 : should throw if target file already exists', function() { it('Test#007 : should throw if target file already exists', function () {
// write out a file // write out a file
var target = path.resolve(temp, 'src/com/phonegap/plugins/dummyplugin'); var target = path.resolve(temp, 'src/com/phonegap/plugins/dummyplugin');
shell.mkdir('-p', target); shell.mkdir('-p', target);
target = path.join(target, 'DummyPlugin.java'); target = path.join(target, 'DummyPlugin.java');
fs.writeFileSync(target, 'some bs', 'utf-8'); fs.writeFileSync(target, 'some bs', 'utf-8');
expect(function() { expect(function () {
android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject); android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject);
}).toThrow(new Error ('"' + target + '" already exists!')); }).toThrow(new Error('"' + target + '" already exists!'));
}); });
}); });
describe('of <framework> elements', function() { describe('of <framework> elements', function () {
var someString = jasmine.any(String); var someString = jasmine.any(String);
var copyNewFileOrig = common.__get__('copyNewFile'); var copyNewFileOrig = common.__get__('copyNewFile');
var copyNewFileSpy = jasmine.createSpy('copyNewFile'); var copyNewFileSpy = jasmine.createSpy('copyNewFile');
beforeEach(function() { beforeEach(function () {
shell.cp('-rf', android_project, temp); shell.cp('-rf', android_project, temp);
spyOn(dummyProject, 'addSystemLibrary'); spyOn(dummyProject, 'addSystemLibrary');
@ -130,27 +130,27 @@ describe('android project handler', function() {
common.__set__('copyNewFile', copyNewFileSpy); common.__set__('copyNewFile', copyNewFileSpy);
}); });
afterEach(function() { afterEach(function () {
common.__set__('copyNewFile', copyNewFileOrig); common.__set__('copyNewFile', copyNewFileOrig);
}); });
it('Test#008 : should throw if framework doesn\'t have "src" attribute', function() { it('Test#008 : should throw if framework doesn\'t have "src" attribute', function () {
expect(function() { android.framework.install({}, dummyPluginInfo, dummyProject); }).toThrow(); expect(function () { android.framework.install({}, dummyPluginInfo, dummyProject); }).toThrow();
}); });
it('Test#009 : should install framework without "parent" attribute into project root', function() { it('Test#009 : should install framework without "parent" attribute into project root', function () {
var framework = {src: 'plugin-lib'}; var framework = {src: 'plugin-lib'};
android.framework.install(framework, dummyPluginInfo, dummyProject); android.framework.install(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(dummyProject.projectDir, someString);
}); });
it('Test#010 : should install framework with "parent" attribute into parent framework dir', function() { it('Test#010 : should install framework with "parent" attribute into parent framework dir', function () {
var childFramework = {src: 'plugin-lib2', parent: 'plugin-lib'}; var childFramework = {src: 'plugin-lib2', parent: 'plugin-lib'};
android.framework.install(childFramework, dummyPluginInfo, dummyProject); android.framework.install(childFramework, dummyPluginInfo, dummyProject);
expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(path.resolve(dummyProject.projectDir, childFramework.parent), someString); expect(dummyProject.addSystemLibrary).toHaveBeenCalledWith(path.resolve(dummyProject.projectDir, childFramework.parent), someString);
}); });
it('Test#011 : should not copy anything if "custom" attribute is not set', function() { it('Test#011 : should not copy anything if "custom" attribute is not set', function () {
var framework = {src: 'plugin-lib'}; var framework = {src: 'plugin-lib'};
var cpSpy = spyOn(shell, 'cp'); var cpSpy = spyOn(shell, 'cp');
android.framework.install(framework, dummyPluginInfo, dummyProject); android.framework.install(framework, dummyPluginInfo, dummyProject);
@ -158,14 +158,14 @@ describe('android project handler', function() {
expect(cpSpy).not.toHaveBeenCalled(); expect(cpSpy).not.toHaveBeenCalled();
}); });
it('Test#012 : should copy framework sources if "custom" attribute is set', function() { it('Test#012 : should copy framework sources if "custom" attribute is set', function () {
var framework = {src: 'plugin-lib', custom: true}; var framework = {src: 'plugin-lib', custom: true};
android.framework.install(framework, dummyPluginInfo, dummyProject); android.framework.install(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.addSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(dummyProject.addSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(copyNewFileSpy).toHaveBeenCalledWith(dummyPluginInfo.dir, framework.src, dummyProject.projectDir, someString, false); expect(copyNewFileSpy).toHaveBeenCalledWith(dummyPluginInfo.dir, framework.src, dummyProject.projectDir, someString, false);
}); });
it('Test#013 : should install gradleReference using project.addGradleReference', function() { it('Test#013 : should install gradleReference using project.addGradleReference', function () {
var framework = {src: 'plugin-lib', custom: true, type: 'gradleReference'}; var framework = {src: 'plugin-lib', custom: true, type: 'gradleReference'};
android.framework.install(framework, dummyPluginInfo, dummyProject); android.framework.install(framework, dummyPluginInfo, dummyProject);
expect(copyNewFileSpy).toHaveBeenCalledWith(dummyPluginInfo.dir, framework.src, dummyProject.projectDir, someString, false); expect(copyNewFileSpy).toHaveBeenCalledWith(dummyPluginInfo.dir, framework.src, dummyProject.projectDir, someString, false);
@ -173,7 +173,7 @@ describe('android project handler', function() {
}); });
}); });
describe('of <js-module> elements', function() { describe('of <js-module> elements', function () {
var jsModule = {src: 'www/dummyplugin.js'}; var jsModule = {src: 'www/dummyplugin.js'};
var wwwDest, platformWwwDest; var wwwDest, platformWwwDest;
@ -196,9 +196,10 @@ describe('android project handler', function() {
}); });
}); });
describe('of <asset> elements', function() { describe('of <asset> elements', function () {
var asset = {src: 'www/dummyPlugin.js', target: 'foo/dummy.js'}; var asset = {src: 'www/dummyPlugin.js', target: 'foo/dummy.js'};
var wwwDest, platformWwwDest; var wwwDest; /* eslint no-unused-vars: "off" */
var platformWwwDest; /* eslint no-unused-vars: "off" */
beforeEach(function () { beforeEach(function () {
wwwDest = path.resolve(dummyProject.www, asset.target); wwwDest = path.resolve(dummyProject.www, asset.target);
@ -219,7 +220,7 @@ describe('android project handler', function() {
}); });
}); });
describe('uninstallation', function() { describe('uninstallation', function () {
var removeFileOrig = common.__get__('removeFile'); var removeFileOrig = common.__get__('removeFile');
var deleteJavaOrig = common.__get__('deleteJava'); var deleteJavaOrig = common.__get__('deleteJava');
@ -228,7 +229,7 @@ describe('android project handler', function() {
var deleteJavaSpy = jasmine.createSpy('deleteJava'); var deleteJavaSpy = jasmine.createSpy('deleteJava');
var dummyProject; var dummyProject;
beforeEach(function() { beforeEach(function () {
shell.mkdir('-p', temp); shell.mkdir('-p', temp);
shell.mkdir('-p', plugins_dir); shell.mkdir('-p', plugins_dir);
shell.cp('-rf', android_project, temp); shell.cp('-rf', android_project, temp);
@ -238,26 +239,26 @@ describe('android project handler', function() {
common.__set__('deleteJava', deleteJavaSpy); common.__set__('deleteJava', deleteJavaSpy);
}); });
afterEach(function() { afterEach(function () {
shell.rm('-rf', temp); shell.rm('-rf', temp);
common.__set__('removeFile', removeFileOrig); common.__set__('removeFile', removeFileOrig);
common.__set__('deleteJava', deleteJavaOrig); common.__set__('deleteJava', deleteJavaOrig);
}); });
describe('of <lib-file> elements', function() { describe('of <lib-file> elements', function () {
it('Test#018 : should remove jar files', function () { it('Test#018 : should remove jar files', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject); android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject);
android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject); android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('libs/TestLib.jar')); expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('libs/TestLib.jar'));
}); });
it('Test#019 : should remove jar files for Android Studio projects', function () { it('Test#019 : should remove jar files for Android Studio projects', function () {
android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject, {android_studio:true}); android['lib-file'].install(valid_libs[0], dummyPluginInfo, dummyProject, {android_studio: true});
android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject, {android_studio:true}); android['lib-file'].uninstall(valid_libs[0], dummyPluginInfo, dummyProject, {android_studio: true});
expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestLib.jar')); expect(removeFileSpy).toHaveBeenCalledWith(temp, path.join('app/libs/TestLib.jar'));
}); });
}); });
describe('of <resource-file> elements', function() { describe('of <resource-file> elements', function () {
it('Test#020 : should remove files', function () { it('Test#020 : should remove files', function () {
android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject); android['resource-file'].install(valid_resources[0], dummyPluginInfo, dummyProject);
android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject); android['resource-file'].uninstall(valid_resources[0], dummyPluginInfo, dummyProject);
@ -265,24 +266,24 @@ describe('android project handler', function() {
}); });
}); });
describe('of <source-file> elements', function() { describe('of <source-file> elements', function () {
it('Test#021 : should remove stuff by calling common.deleteJava', function() { it('Test#021 : should remove stuff by calling common.deleteJava', function () {
android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject); android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject);
android['source-file'].uninstall(valid_source[0], dummyPluginInfo, dummyProject); android['source-file'].uninstall(valid_source[0], dummyPluginInfo, dummyProject);
expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('src/com/phonegap/plugins/dummyplugin/DummyPlugin.java')); expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('src/com/phonegap/plugins/dummyplugin/DummyPlugin.java'));
}); });
it('Test#022 : should remove stuff by calling common.deleteJava for Android Studio projects', function() { it('Test#022 : should remove stuff by calling common.deleteJava for Android Studio projects', function () {
android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject, {android_studio:true}); android['source-file'].install(valid_source[0], dummyPluginInfo, dummyProject, {android_studio: true});
android['source-file'].uninstall(valid_source[0], dummyPluginInfo, dummyProject, {android_studio:true}); android['source-file'].uninstall(valid_source[0], dummyPluginInfo, dummyProject, {android_studio: true});
expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/java/com/phonegap/plugins/dummyplugin/DummyPlugin.java')); expect(deleteJavaSpy).toHaveBeenCalledWith(temp, path.join('app/src/main/java/com/phonegap/plugins/dummyplugin/DummyPlugin.java'));
}); });
}); });
describe('of <framework> elements', function() { describe('of <framework> elements', function () {
var someString = jasmine.any(String); var someString = jasmine.any(String);
beforeEach(function() { beforeEach(function () {
shell.mkdir(path.join(dummyProject.projectDir, dummyPluginInfo.id)); shell.mkdir(path.join(dummyProject.projectDir, dummyPluginInfo.id));
spyOn(dummyProject, 'removeSystemLibrary'); spyOn(dummyProject, 'removeSystemLibrary');
@ -290,30 +291,30 @@ describe('android project handler', function() {
spyOn(dummyProject, 'removeGradleReference'); spyOn(dummyProject, 'removeGradleReference');
}); });
it('Test#023 : should throw if framework doesn\'t have "src" attribute', function() { it('Test#023 : should throw if framework doesn\'t have "src" attribute', function () {
expect(function() { android.framework.uninstall({}, dummyPluginInfo, dummyProject); }).toThrow(); expect(function () { android.framework.uninstall({}, dummyPluginInfo, dummyProject); }).toThrow();
}); });
it('Test#024 : should uninstall framework without "parent" attribute into project root', function() { it('Test#024 : should uninstall framework without "parent" attribute into project root', function () {
var framework = {src: 'plugin-lib'}; var framework = {src: 'plugin-lib'};
android.framework.uninstall(framework, dummyPluginInfo, dummyProject); android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSystemLibrary).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(dummyProject.removeSystemLibrary).toHaveBeenCalledWith(dummyProject.projectDir, someString);
}); });
it('Test#025 : should uninstall framework with "parent" attribute into parent framework dir', function() { it('Test#025 : should uninstall framework with "parent" attribute into parent framework dir', function () {
var childFramework = {src: 'plugin-lib2', parent: 'plugin-lib'}; var childFramework = {src: 'plugin-lib2', parent: 'plugin-lib'};
android.framework.uninstall(childFramework, dummyPluginInfo, dummyProject); android.framework.uninstall(childFramework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSystemLibrary).toHaveBeenCalledWith(path.resolve(dummyProject.projectDir, childFramework.parent), someString); expect(dummyProject.removeSystemLibrary).toHaveBeenCalledWith(path.resolve(dummyProject.projectDir, childFramework.parent), someString);
}); });
it('Test#026 : should remove framework sources if "custom" attribute is set', function() { it('Test#026 : should remove framework sources if "custom" attribute is set', function () {
var framework = {src: 'plugin-lib', custom: true}; var framework = {src: 'plugin-lib', custom: true};
android.framework.uninstall(framework, dummyPluginInfo, dummyProject); android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(dummyProject.removeSubProject).toHaveBeenCalledWith(dummyProject.projectDir, someString);
expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
}); });
it('Test#27 : should install gradleReference using project.removeGradleReference', function() { it('Test#27 : should install gradleReference using project.removeGradleReference', function () {
var framework = {src: 'plugin-lib', custom: true, type: 'gradleReference'}; var framework = {src: 'plugin-lib', custom: true, type: 'gradleReference'};
android.framework.uninstall(framework, dummyPluginInfo, dummyProject); android.framework.uninstall(framework, dummyPluginInfo, dummyProject);
expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString); expect(removeFileSpy).toHaveBeenCalledWith(dummyProject.projectDir, someString);
@ -321,10 +322,10 @@ describe('android project handler', function() {
}); });
}); });
describe('of <js-module> elements', function () {
describe('of <js-module> elements', function() {
var jsModule = {src: 'www/dummyPlugin.js'}; var jsModule = {src: 'www/dummyPlugin.js'};
var wwwDest, platformWwwDest; var wwwDest;
var platformWwwDest;
beforeEach(function () { beforeEach(function () {
wwwDest = path.resolve(dummyProject.www, 'plugins', dummyPluginInfo.id, jsModule.src); wwwDest = path.resolve(dummyProject.www, 'plugins', dummyPluginInfo.id, jsModule.src);
@ -334,7 +335,7 @@ describe('android project handler', function() {
var existsSyncOrig = fs.existsSync; var existsSyncOrig = fs.existsSync;
spyOn(fs, 'existsSync').and.callFake(function (file) { spyOn(fs, 'existsSync').and.callFake(function (file) {
if ([wwwDest, platformWwwDest].indexOf(file) >= 0 ) return true; if ([wwwDest, platformWwwDest].indexOf(file) >= 0) return true;
return existsSyncOrig.call(fs, file); return existsSyncOrig.call(fs, file);
}); });
}); });
@ -352,7 +353,7 @@ describe('android project handler', function() {
}); });
}); });
describe('of <asset> elements', function() { describe('of <asset> elements', function () {
var asset = {src: 'www/dummyPlugin.js', target: 'foo/dummy.js'}; var asset = {src: 'www/dummyPlugin.js', target: 'foo/dummy.js'};
var wwwDest, platformWwwDest; var wwwDest, platformWwwDest;
@ -364,7 +365,7 @@ describe('android project handler', function() {
var existsSyncOrig = fs.existsSync; var existsSyncOrig = fs.existsSync;
spyOn(fs, 'existsSync').and.callFake(function (file) { spyOn(fs, 'existsSync').and.callFake(function (file) {
if ([wwwDest, platformWwwDest].indexOf(file) >= 0 ) return true; if ([wwwDest, platformWwwDest].indexOf(file) >= 0) return true;
return existsSyncOrig.call(fs, file); return existsSyncOrig.call(fs, file);
}); });
}); });

View File

@ -17,22 +17,22 @@
under the License. under the License.
*/ */
var rewire = require("rewire"); var rewire = require('rewire');
var run = rewire("../../bin/templates/cordova/lib/run"); var run = rewire('../../bin/templates/cordova/lib/run');
var getInstallTarget = run.__get__("getInstallTarget"); var getInstallTarget = run.__get__('getInstallTarget');
describe("run", function () { describe('run', function () {
describe("getInstallTarget", function() { describe('getInstallTarget', function () {
var targetOpts = { target: "emu" }; var targetOpts = { target: 'emu' };
var deviceOpts = { device: true }; var deviceOpts = { device: true };
var emulatorOpts = { emulator: true }; var emulatorOpts = { emulator: true };
var emptyOpts = {}; var emptyOpts = {};
it("Test#001 : should select correct target based on the run opts", function() { it('Test#001 : should select correct target based on the run opts', function () {
expect(getInstallTarget(targetOpts)).toBe("emu"); expect(getInstallTarget(targetOpts)).toBe('emu');
expect(getInstallTarget(deviceOpts)).toBe("--device"); expect(getInstallTarget(deviceOpts)).toBe('--device');
expect(getInstallTarget(emulatorOpts)).toBe("--emulator"); expect(getInstallTarget(emulatorOpts)).toBe('--emulator');
expect(getInstallTarget(emptyOpts)).toBeUndefined(); expect(getInstallTarget(emptyOpts)).toBeUndefined();
});
}); });
});
}); });