Fixed line endings

This commit is contained in:
Steve Gill 2015-10-20 16:15:57 -07:00
parent 400282282f
commit 0ac822c577
19 changed files with 3537 additions and 3537 deletions

View File

@ -1,4 +1,4 @@

Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004
http://www.apache.org/licenses/ http://www.apache.org/licenses/

View File

@ -5,9 +5,9 @@
:: to you under the Apache License, Version 2.0 (the :: to you under the Apache License, Version 2.0 (the
:: "License"); you may not use this file except in compliance :: "License"); you may not use this file except in compliance
:: with the License. You may obtain a copy of the License at :: with the License. You may obtain a copy of the License at
:: ::
:: http://www.apache.org/licenses/LICENSE-2.0 :: http://www.apache.org/licenses/LICENSE-2.0
:: ::
:: Unless required by applicable law or agreed to in writing, :: Unless required by applicable law or agreed to in writing,
:: software distributed under the License is distributed on an :: software distributed under the License is distributed on an
:: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY :: "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

View File

@ -1,56 +1,56 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var path = require('path'); var path = require('path');
var ConfigParser = require('cordova-common').ConfigParser; var ConfigParser = require('cordova-common').ConfigParser;
var Api = require('./templates/cordova/Api'); var Api = require('./templates/cordova/Api');
var argv = require('nopt')({ var argv = require('nopt')({
'help' : Boolean, 'help' : Boolean,
'cli' : Boolean, 'cli' : Boolean,
'shared' : Boolean, 'shared' : Boolean,
'link' : Boolean, 'link' : Boolean,
'activity-name' : [String, undefined] 'activity-name' : [String, undefined]
}); });
if (argv.help || argv.argv.remain.length === 0) { if (argv.help || argv.argv.remain.length === 0) {
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'create')) + ' <path_to_new_project> <package_name> <project_name> [<template_path>] [--activity-name <activity_name>] [--link]'); console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'create')) + ' <path_to_new_project> <package_name> <project_name> [<template_path>] [--activity-name <activity_name>] [--link]');
console.log(' <path_to_new_project>: Path to your new Cordova Android project'); console.log(' <path_to_new_project>: Path to your new Cordova Android project');
console.log(' <package_name>: Package name, following reverse-domain style convention'); console.log(' <package_name>: Package name, following reverse-domain style convention');
console.log(' <project_name>: Project name'); console.log(' <project_name>: Project name');
console.log(' <template_path>: Path to a custom application template to use'); console.log(' <template_path>: Path to a custom application template to use');
console.log(' --activity-name <activity_name>: Activity name'); console.log(' --activity-name <activity_name>: Activity name');
console.log(' --link will use the CordovaLib project directly instead of making a copy.'); console.log(' --link will use the CordovaLib project directly instead of making a copy.');
process.exit(1); process.exit(1);
} }
var config = new ConfigParser(path.resolve(__dirname, 'templates/project/res/xml/config.xml')); var config = new ConfigParser(path.resolve(__dirname, 'templates/project/res/xml/config.xml'));
if (argv.argv.remain[1]) config.setPackageName(argv.argv.remain[1]); if (argv.argv.remain[1]) config.setPackageName(argv.argv.remain[1]);
if (argv.argv.remain[2]) config.setName(argv.argv.remain[2]); if (argv.argv.remain[2]) config.setName(argv.argv.remain[2]);
if (argv['activity-name']) config.setName(argv['activity-name']); if (argv['activity-name']) config.setName(argv['activity-name']);
var options = { var options = {
link: argv.link || argv.shared, link: argv.link || argv.shared,
customTemplate: argv.argv.remain[3], customTemplate: argv.argv.remain[3],
activityName: argv['activity-name'] activityName: argv['activity-name']
}; };
Api.createPlatform(argv.argv.remain[0], config, options).done(); Api.createPlatform(argv.argv.remain[0], config, options).done();

View File

@ -1,326 +1,326 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* jshint sub:true */ /* jshint sub:true */
var shelljs = require('shelljs'), var shelljs = require('shelljs'),
child_process = require('child_process'), child_process = require('child_process'),
Q = require('q'), Q = require('q'),
path = require('path'), path = require('path'),
fs = require('fs'), fs = require('fs'),
ROOT = path.join(__dirname, '..', '..'); ROOT = path.join(__dirname, '..', '..');
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var isWindows = process.platform == 'win32'; var isWindows = process.platform == 'win32';
function forgivingWhichSync(cmd) { function forgivingWhichSync(cmd) {
try { try {
return fs.realpathSync(shelljs.which(cmd)); return fs.realpathSync(shelljs.which(cmd));
} catch (e) { } catch (e) {
return ''; return '';
} }
} }
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
else d.resolve((catchStderr ? stderr : stdout).trim()); else d.resolve((catchStderr ? stderr : stdout).trim());
}); });
return d.promise; return d.promise;
} }
// Get valid target from framework/project.properties // Get valid target from framework/project.properties
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);
} }
return target.split('=')[1].trim(); return target.split('=')[1].trim();
} }
if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) { if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
return extractFromFile(path.join(ROOT, 'framework', 'project.properties')); return extractFromFile(path.join(ROOT, 'framework', 'project.properties'));
} }
if (fs.existsSync(path.join(ROOT, 'project.properties'))) { if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
// if no target found, we're probably in a project and project.properties is in ROOT. // if no target found, we're probably in a project and project.properties is in ROOT.
return extractFromFile(path.join(ROOT, 'project.properties')); return extractFromFile(path.join(ROOT, 'project.properties'));
} }
throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties')); throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties'));
}; };
// 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 tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.') return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.')
.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];
}); });
}; };
// 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'];
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 wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (!fs.existsSync(wrapperDir)) { if (!fs.existsSync(wrapperDir)) {
return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' + return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
'Looked here: ' + wrapperDir)); 'Looked here: ' + wrapperDir));
} }
return Q.when(); return Q.when();
}; };
// 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) {
process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin'); process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin');
} }
} else { } else {
if (javacPath) { if (javacPath) {
var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.'; var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.';
// OS X has a command for finding JAVA_HOME. // OS X has a command for finding JAVA_HOME.
if (fs.existsSync('/usr/libexec/java_home')) { if (fs.existsSync('/usr/libexec/java_home')) {
return tryCommand('/usr/libexec/java_home', msg) return tryCommand('/usr/libexec/java_home', msg)
.then(function(stdout) { .then(function(stdout) {
process.env['JAVA_HOME'] = stdout.trim(); process.env['JAVA_HOME'] = stdout.trim();
}); });
} else { } else {
// See if we can derive it from javac's location. // See if we can derive it from javac's location.
// fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK // fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
var maybeJavaHome = path.dirname(path.dirname(javacPath)); var maybeJavaHome = path.dirname(path.dirname(javacPath));
if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) { if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
process.env['JAVA_HOME'] = maybeJavaHome; process.env['JAVA_HOME'] = maybeJavaHome;
} else { } else {
throw new CordovaError(msg); throw new CordovaError(msg);
} }
} }
} else if (isWindows) { } else if (isWindows) {
// Try to auto-detect java in the default install paths. // Try to auto-detect java in the default install paths.
var oldSilent = shelljs.config.silent; var oldSilent = shelljs.config.silent;
shelljs.config.silent = true; shelljs.config.silent = true;
var firstJdkDir = var firstJdkDir =
shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] || shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files\\java\\jdk*')[0] || shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0]; shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
shelljs.config.silent = oldSilent; shelljs.config.silent = oldSilent;
if (firstJdkDir) { if (firstJdkDir) {
// shelljs always uses / in paths. // shelljs always uses / in paths.
firstJdkDir = firstJdkDir.replace(/\//g, path.sep); firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
if (!javacPath) { if (!javacPath) {
process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin'); process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');
} }
process.env['JAVA_HOME'] = firstJdkDir; process.env['JAVA_HOME'] = firstJdkDir;
} }
} }
} }
}).then(function() { }).then(function() {
var msg = var msg =
'Failed to run "java -version", make sure that you have a JDK installed.\n' + 'Failed to run "java -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';
if (process.env['JAVA_HOME']) { if (process.env['JAVA_HOME']) {
msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n'; msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
} }
return tryCommand('java -version', msg) return tryCommand('java -version', msg)
.then(function() { .then(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) {
var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
return match && match[1]; return match && match[1];
}); });
}); });
}; };
// 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 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;
} }
} }
if (!hasAndroidHome && !androidCmdPath) { if (!hasAndroidHome && !androidCmdPath) {
if (isWindows) { if (isWindows) {
// Android Studio 1.0 installer // Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk')); maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk')); maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk'));
// Android Studio pre-1.0 installer // Android Studio pre-1.0 installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk')); maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk')); maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
// Stand-alone installer // Stand-alone installer
maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk')); maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk')); maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
} else if (process.platform == 'darwin') { } else if (process.platform == 'darwin') {
// Android Studio 1.0 installer // Android Studio 1.0 installer
maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk')); maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk'));
// Android Studio pre-1.0 installer // Android Studio pre-1.0 installer
maybeSetAndroidHome('/Applications/Android Studio.app/sdk'); maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
// Stand-alone zip file that user might think to put under /Applications // Stand-alone zip file that user might think to put under /Applications
maybeSetAndroidHome('/Applications/android-sdk-macosx'); maybeSetAndroidHome('/Applications/android-sdk-macosx');
maybeSetAndroidHome('/Applications/android-sdk'); maybeSetAndroidHome('/Applications/android-sdk');
} }
if (process.env['HOME']) { if (process.env['HOME']) {
// Stand-alone zip file that user might think to put under their home directory // Stand-alone zip file that user might think to put under their home directory
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx')); maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk')); maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
} }
} }
if (hasAndroidHome && !androidCmdPath) { if (hasAndroidHome && !androidCmdPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
} }
if (androidCmdPath && !hasAndroidHome) { if (androidCmdPath && !hasAndroidHome) {
var parentDir = path.dirname(androidCmdPath); var parentDir = path.dirname(androidCmdPath);
var grandParentDir = path.dirname(parentDir); var grandParentDir = path.dirname(parentDir);
if (path.basename(parentDir) == 'tools') { if (path.basename(parentDir) == 'tools') {
process.env['ANDROID_HOME'] = path.dirname(parentDir); process.env['ANDROID_HOME'] = path.dirname(parentDir);
hasAndroidHome = true; hasAndroidHome = true;
} else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) { } else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
process.env['ANDROID_HOME'] = grandParentDir; process.env['ANDROID_HOME'] = grandParentDir;
hasAndroidHome = true; hasAndroidHome = true;
} 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' +
'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' + 'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.'); 'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.');
} }
} }
if (hasAndroidHome && !adbInPath) { if (hasAndroidHome && !adbInPath) {
process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
} }
if (!process.env['ANDROID_HOME']) { if (!process.env['ANDROID_HOME']) {
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' +
'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.'); 'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
} }
if (!fs.existsSync(process.env['ANDROID_HOME'])) { if (!fs.existsSync(process.env['ANDROID_HOME'])) {
throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] + throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
'\nTry update it manually to point to valid SDK directory.'); '\nTry update it manually to point to valid SDK directory.');
} }
}); });
}; };
module.exports.getAbsoluteAndroidCmd = function() { module.exports.getAbsoluteAndroidCmd = function() {
return forgivingWhichSync('android').replace(/(\s)/g, '\\$1'); return forgivingWhichSync('android').replace(/(\s)/g, '\\$1');
}; };
module.exports.check_android_target = function(valid_target) { module.exports.check_android_target = function(valid_target) {
// 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
if (!valid_target) valid_target = module.exports.get_target(); if (!valid_target) valid_target = module.exports.get_target();
var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.'; var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.';
return tryCommand('android list targets --compact', msg) return tryCommand('android list targets --compact', msg)
.then(function(output) { .then(function(output) {
var targets = output.split('\n'); var targets = output.split('\n');
if (targets.indexOf(valid_target) >= 0) { if (targets.indexOf(valid_target) >= 0) {
return targets; return targets;
} }
var androidCmd = module.exports.getAbsoluteAndroidCmd(); var androidCmd = module.exports.getAbsoluteAndroidCmd();
throw new CordovaError('Please install Android target: "' + valid_target + '".\n\n' + throw new CordovaError('Please install Android target: "' + valid_target + '".\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 ' + valid_target + '\n' + '1. "SDK Platform" for ' + valid_target + '\n' +
'2. "Android SDK Platform-tools (latest)\n' + '2. "Android SDK Platform-tools (latest)\n' +
'3. "Android SDK Build-tools" (latest)'); '3. "Android SDK Build-tools" (latest)');
}); });
}; };
// Returns a promise. // Returns a promise.
module.exports.run = function() { module.exports.run = function() {
return Q.all([this.check_java(), this.check_android().then(this.check_android_target)]) return Q.all([this.check_java(), this.check_android().then(this.check_android_target)])
.then(function() { .then(function() {
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']);
}); });
}; };
/** /**
* 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.
* @param {String} name The name of requirements. Human-readable field. * @param {String} name The name of requirements. Human-readable field.
* @param {String} version The version of requirement installed. In some cases could be an array of strings * @param {String} version The version of requirement installed. In some cases could be an array of strings
* (for example, check_android_target returns an array of android targets installed) * (for example, check_android_target returns an array of android targets installed)
* @param {Boolean} installed Indicates whether the requirement is installed or not * @param {Boolean} installed Indicates whether the requirement is installed or not
*/ */
var Requirement = function (id, name, version, installed) { var Requirement = function (id, name, version, installed) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.installed = installed || false; this.installed = installed || false;
this.metadata = { this.metadata = {
version: version, version: version,
}; };
}; };
/** /**
* Methods that runs all checks one by one and returns a result of checks * Methods that runs all checks one by one and returns a result of checks
* as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method * as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method
* *
* @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'),
new Requirement('androidSdk', 'Android SDK'), new Requirement('androidSdk', 'Android SDK'),
new Requirement('androidTarget', 'Android target'), new Requirement('androidTarget', 'Android target'),
new Requirement('gradle', 'Gradle') new Requirement('gradle', 'Gradle')
]; ];
var checkFns = [ var checkFns = [
this.check_java, this.check_java,
this.check_android, this.check_android,
this.check_android_target, this.check_android_target,
this.check_gradle this.check_gradle
]; ];
// Then execute requirement checks one-by-one // Then execute requirement checks one-by-one
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

@ -1,332 +1,332 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var shell = require('shelljs'), var shell = require('shelljs'),
Q = require('q'), Q = require('q'),
path = require('path'), path = require('path'),
fs = require('fs'), fs = require('fs'),
check_reqs = require('./check_reqs'), check_reqs = require('./check_reqs'),
ROOT = path.join(__dirname, '..', '..'); ROOT = path.join(__dirname, '..', '..');
var MIN_SDK_VERSION = 14; var MIN_SDK_VERSION = 14;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
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');
} }
function copyJsAndLibrary(projectPath, shared, projectName) { function copyJsAndLibrary(projectPath, shared, projectName) {
var nestedCordovaLibPath = getFrameworkDir(projectPath, false); var nestedCordovaLibPath = getFrameworkDir(projectPath, false);
var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js'); var srcCordovaJsPath = path.join(ROOT, 'bin', 'templates', 'project', 'assets', 'www', 'cordova.js');
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'assets', 'www', 'cordova.js')); shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'assets', 'www', 'cordova.js'));
// Copy the cordova.js file to platforms/<platform>/platform_www/ // Copy the cordova.js file to platforms/<platform>/platform_www/
// The www dir is nuked on each prepare so we keep cordova.js in platform_www // The www dir is nuked on each prepare so we keep cordova.js in platform_www
shell.mkdir('-p', path.join(projectPath, 'platform_www')); shell.mkdir('-p', path.join(projectPath, 'platform_www'));
shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'platform_www')); shell.cp('-f', srcCordovaJsPath, path.join(projectPath, 'platform_www'));
// Copy cordova-js-src directory into platform_www directory. // Copy cordova-js-src directory into platform_www directory.
// 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. // Don't fail if there are no old jars.
setShellFatal(false, function() { setShellFatal(false, function() {
shell.ls(path.join(projectPath, '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);
}); });
var wasSymlink = true; var wasSymlink = true;
try { try {
// Delete the symlink if it was one. // Delete the symlink if it was one.
fs.unlinkSync(nestedCordovaLibPath); fs.unlinkSync(nestedCordovaLibPath);
} catch (e) { } catch (e) {
wasSymlink = false; wasSymlink = false;
} }
// Delete old library project if it existed. // Delete old library project if it existed.
if (shared) { if (shared) {
shell.rm('-rf', nestedCordovaLibPath); shell.rm('-rf', nestedCordovaLibPath);
} else if (!wasSymlink) { } else if (!wasSymlink) {
// Delete only the src, since Eclipse / Android Studio can't handle their project files being deleted. // Delete only the src, since Eclipse / Android Studio can't handle their project files being deleted.
shell.rm('-rf', path.join(nestedCordovaLibPath, 'src')); shell.rm('-rf', path.join(nestedCordovaLibPath, 'src'));
} }
}); });
if (shared) { if (shared) {
var relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true)); var relativeFrameworkPath = path.relative(projectPath, getFrameworkDir(projectPath, true));
fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir'); fs.symlinkSync(relativeFrameworkPath, nestedCordovaLibPath, 'dir');
} else { } else {
shell.mkdir('-p', nestedCordovaLibPath); shell.mkdir('-p', nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath); shell.cp('-f', path.join(ROOT, 'framework', 'AndroidManifest.xml'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath); shell.cp('-f', path.join(ROOT, 'framework', 'project.properties'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath); shell.cp('-f', path.join(ROOT, 'framework', 'build.gradle'), nestedCordovaLibPath);
shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath); shell.cp('-f', path.join(ROOT, 'framework', 'cordova.gradle'), nestedCordovaLibPath);
shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath); shell.cp('-r', path.join(ROOT, 'framework', 'src'), nestedCordovaLibPath);
} }
} }
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;
while ((m = r.exec(data))) { while ((m = r.exec(data))) {
ret[m[1]] = 1; ret[m[1]] = 1;
} }
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;
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, '');
if (!/\n$/.exec(data)) { if (!/\n$/.exec(data)) {
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);
} }
function prepBuildFiles(projectPath) { function prepBuildFiles(projectPath) {
var buildModule = require(path.join(path.resolve(projectPath), 'cordova', 'lib', 'build')); var buildModule = require(path.join(path.resolve(projectPath), 'cordova', 'lib', 'build'));
buildModule.prepBuildFiles(); buildModule.prepBuildFiles();
} }
function copyBuildRules(projectPath) { function copyBuildRules(projectPath) {
var srcDir = path.join(ROOT, 'bin', 'templates', 'project'); var srcDir = path.join(ROOT, 'bin', 'templates', 'project');
shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath); shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath);
} }
function copyScripts(projectPath) { function copyScripts(projectPath) {
var srcScriptsDir = path.join(ROOT, 'bin', 'templates', 'cordova'); var srcScriptsDir = path.join(ROOT, 'bin', 'templates', 'cordova');
var destScriptsDir = path.join(projectPath, 'cordova'); var destScriptsDir = path.join(projectPath, 'cordova');
// Delete old scripts directory if this is an update. // Delete old scripts directory if this is an update.
shell.rm('-rf', destScriptsDir); shell.rm('-rf', destScriptsDir);
// Copy in the new ones. // Copy in the new ones.
shell.cp('-r', srcScriptsDir, projectPath); shell.cp('-r', srcScriptsDir, projectPath);
shell.cp('-r', path.join(ROOT, 'node_modules'), destScriptsDir); shell.cp('-r', path.join(ROOT, 'node_modules'), destScriptsDir);
shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir); shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir);
shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js')); shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js'));
shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version')); shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version'));
shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js')); shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js'));
} }
/** /**
* Test whether a package name is acceptable for use as an android project. * Test whether a package name is acceptable for use as an android project.
* 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'));
} }
return Q.resolve(); return Q.resolve();
} }
/** /**
* Test whether a project name is acceptable for use as an android class. * Test whether a project name is acceptable for use as an android class.
* 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'));
} }
return Q.resolve(); return Q.resolve();
} }
/** /**
* Creates an android application with the given options. * Creates an android application with the given options.
* *
* @param {String} project_path Path to the new Cordova android project. * @param {String} project_path Path to the new Cordova android project.
* @param {ConfigParser} config Instance of ConfigParser to retrieve basic * @param {ConfigParser} config Instance of ConfigParser to retrieve basic
* project properties. * project properties.
* @param {Object} [options={}] Various options * @param {Object} [options={}] Various options
* @param {String} [options.activityName='MainActivity'] Name for the * @param {String} [options.activityName='MainActivity'] Name for the
* activity * activity
* @param {Boolean} [options.link=false] Specifies whether javascript files * @param {Boolean} [options.link=false] Specifies whether javascript files
* and CordovaLib framework will be symlinked to created application. * and CordovaLib framework will be symlinked to created application.
* @param {String} [options.customTemplate] Path to project template * @param {String} [options.customTemplate] Path to project template
* (override) * (override)
* @param {EventEmitter} [events] An EventEmitter instance for logging * @param {EventEmitter} [events] An EventEmitter instance for logging
* events * events
* *
* @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);
events.emit('log', '\tPackage: ' + package_name); events.emit('log', '\tPackage: ' + package_name);
events.emit('log', '\tName: ' + project_name); events.emit('log', '\tName: ' + project_name);
events.emit('log', '\tActivity: ' + safe_activity_name); events.emit('log', '\tActivity: ' + safe_activity_name);
events.emit('log', '\tAndroid target: ' + target_api); events.emit('log', '\tAndroid target: ' + target_api);
events.emit('verbose', 'Copying template files...'); events.emit('verbose', 'Copying template files...');
setShellFatal(true, function() { setShellFatal(true, function() {
var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project'); var project_template_dir = options.customTemplate || path.join(ROOT, 'bin', 'templates', 'project');
// copy project template // copy project template
shell.cp('-r', path.join(project_template_dir, 'assets'), project_path); shell.cp('-r', path.join(project_template_dir, 'assets'), project_path);
shell.cp('-r', path.join(project_template_dir, 'res'), project_path); shell.cp('-r', path.join(project_template_dir, 'res'), project_path);
shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore')); shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore'));
// Manually create directories that would be empty within the template (since git doesn't track directories). // Manually create directories that would be empty within the template (since git doesn't track directories).
shell.mkdir(path.join(project_path, 'libs')); shell.mkdir(path.join(project_path, 'libs'));
// copy cordova.js, cordova.jar // copy cordova.js, cordova.jar
copyJsAndLibrary(project_path, options.link, safe_activity_name); copyJsAndLibrary(project_path, options.link, safe_activity_name);
// interpolate the activity name and package // interpolate the activity name and package
var packagePath = package_name.replace(/\./g, path.sep); var packagePath = package_name.replace(/\./g, path.sep);
var activity_dir = path.join(project_path, 'src', packagePath); var activity_dir = path.join(project_path, 'src', packagePath);
var activity_path = path.join(activity_dir, safe_activity_name + '.java'); var activity_path = path.join(activity_dir, safe_activity_name + '.java');
shell.mkdir('-p', activity_dir); shell.mkdir('-p', activity_dir);
shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path); shell.cp('-f', path.join(project_template_dir, 'Activity.java'), activity_path);
shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path); shell.sed('-i', /__ACTIVITY__/, safe_activity_name, activity_path);
shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml')); shell.sed('-i', /__NAME__/, project_name, path.join(project_path, 'res', 'values', 'strings.xml'));
shell.sed('-i', /__ID__/, package_name, activity_path); shell.sed('-i', /__ID__/, package_name, activity_path);
var manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml')); var manifest = new AndroidManifest(path.join(project_template_dir, 'AndroidManifest.xml'));
manifest.setPackageId(package_name) manifest.setPackageId(package_name)
.setTargetSdkVersion(target_api.split('-')[1]) .setTargetSdkVersion(target_api.split('-')[1])
.getActivity().setName(safe_activity_name); .getActivity().setName(safe_activity_name);
var manifest_path = path.join(project_path, 'AndroidManifest.xml'); var manifest_path = path.join(project_path, 'AndroidManifest.xml');
manifest.write(manifest_path); manifest.write(manifest_path);
copyScripts(project_path); copyScripts(project_path);
copyBuildRules(project_path); copyBuildRules(project_path);
}); });
// Link it to local android install. // Link it to local android install.
writeProjectProperties(project_path, target_api); writeProjectProperties(project_path, target_api);
prepBuildFiles(project_path); prepBuildFiles(project_path);
events.emit('log', generateDoneMessage('create', options.link)); events.emit('log', generateDoneMessage('create', options.link));
}).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';
} }
return msg; return msg;
} }
// 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 manifest = new AndroidManifest(path.join(projectPath, 'AndroidManifest.xml')); var manifest = new AndroidManifest(path.join(projectPath, 'AndroidManifest.xml'));
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); copyJsAndLibrary(projectPath, options.link, projectName);
copyScripts(projectPath); copyScripts(projectPath);
copyBuildRules(projectPath); copyBuildRules(projectPath);
writeProjectProperties(projectPath, target_api); writeProjectProperties(projectPath, target_api);
prepBuildFiles(projectPath); prepBuildFiles(projectPath);
events.emit('log', generateDoneMessage('update', options.link)); events.emit('log', generateDoneMessage('update', options.link));
}).thenResolve(projectPath); }).thenResolve(projectPath);
}; };
// For testing // For testing
exports.validatePackageName = validatePackageName; exports.validatePackageName = validatePackageName;
exports.validateProjectName = validateProjectName; exports.validateProjectName = validateProjectName;

View File

@ -1,492 +1,492 @@
/** /**
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var Q = require('q'); var Q = require('q');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var shell = require('shelljs'); var shell = require('shelljs');
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var PlatformJson = require('cordova-common').PlatformJson; var PlatformJson = require('cordova-common').PlatformJson;
var ActionStack = require('cordova-common').ActionStack; var ActionStack = require('cordova-common').ActionStack;
var AndroidProject = require('./lib/AndroidProject'); var AndroidProject = require('./lib/AndroidProject');
var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger; var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
var PluginInfoProvider = require('cordova-common').PluginInfoProvider; var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
var ConsoleLogger = require('./lib/ConsoleLogger'); var ConsoleLogger = require('./lib/ConsoleLogger');
var pluginHandlers = require('./lib/pluginHandlers'); var pluginHandlers = require('./lib/pluginHandlers');
var PLATFORM = 'android'; var PLATFORM = 'android';
/** /**
* 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.
* *
* Platform that implements own PlatformApi instance _should implement all * Platform that implements own PlatformApi instance _should implement all
* prototype methods_ of this class to be fully compatible with cordova-lib. * prototype methods_ of this class to be fully compatible with cordova-lib.
* *
* The PlatformApi instance also should define the following field: * The PlatformApi instance also should define the following field:
* *
* * 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.events = events || ConsoleLogger.get(); this.events = events || ConsoleLogger.get();
// NOTE: trick to share one EventEmitter instance across all js code // NOTE: trick to share one EventEmitter instance across all js code
require('cordova-common').events = this.events; require('cordova-common').events = this.events;
this._platformJson = PlatformJson.load(this.root, platform); this._platformJson = PlatformJson.load(this.root, platform);
this._pluginInfoProvider = new PluginInfoProvider(); this._pluginInfoProvider = new PluginInfoProvider();
this._munger = new PlatformMunger(this.platform, this.root, this._platformJson, this._pluginInfoProvider); this._munger = new PlatformMunger(this.platform, this.root, this._platformJson, this._pluginInfoProvider);
var self = this; var self = this;
this.locations = { this.locations = {
root: self.root, root: self.root,
www: path.join(self.root, 'assets/www'), www: path.join(self.root, 'assets/www'),
platformWww: path.join(self.root, 'platform_www'), platformWww: path.join(self.root, 'platform_www'),
configXml: path.join(self.root, 'res/xml/config.xml'), configXml: path.join(self.root, 'res/xml/config.xml'),
defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'), defaultConfigXml: path.join(self.root, 'cordova/defaults.xml'),
strings: path.join(self.root, 'res/values/strings.xml'), strings: path.join(self.root, 'res/values/strings.xml'),
manifest: path.join(self.root, 'AndroidManifest.xml'), manifest: path.join(self.root, 'AndroidManifest.xml'),
// NOTE: Due to platformApi spec we need to return relative paths here // NOTE: Due to platformApi spec we need to return relative paths here
cordovaJs: 'bin/templates/project/assets/www/cordova.js', cordovaJs: 'bin/templates/project/assets/www/cordova.js',
cordovaJsSrc: 'cordova-js-src' cordovaJsSrc: 'cordova-js-src'
}; };
} }
/** /**
* Installs platform to specified directory and creates a platform project. * Installs platform to specified directory and creates a platform project.
* *
* @param {String} destination Destination directory, where insatll platform to * @param {String} destination Destination directory, where insatll platform to
* @param {ConfigParser} [config] ConfgiParser instance, used to retrieve * @param {ConfigParser} [config] ConfgiParser instance, used to retrieve
* project creation options, such as package id and project name. * project creation options, such as package id and project name.
* @param {Object} [options] An options object. The most common options are: * @param {Object} [options] An options object. The most common options are:
* @param {String} [options.customTemplate] A path to custom template, that * @param {String} [options.customTemplate] A path to custom template, that
* should override the default one from platform. * should override the default one from platform.
* @param {Boolean} [options.link] Flag that indicates that platform's * @param {Boolean} [options.link] Flag that indicates that platform's
* sources will be linked to installed platform instead of copying. * sources will be linked to installed platform instead of copying.
* @param {EventEmitter} [events] An EventEmitter instance that will be used for * @param {EventEmitter} [events] An EventEmitter instance that will be used for
* logging purposes. If no EventEmitter provided, all events will be logged to * logging purposes. If no EventEmitter provided, all events will be logged to
* console * console
* *
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
* instance or rejected with CordovaError. * instance or rejected with CordovaError.
*/ */
Api.createPlatform = function (destination, config, options, events) { Api.createPlatform = function (destination, config, options, events) {
return require('../../lib/create') return require('../../lib/create')
.create(destination, config, options, events || ConsoleLogger.get()) .create(destination, config, options, events || ConsoleLogger.get())
.then(function (destination) { .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);
}); });
}; };
/** /**
* Updates already installed platform. * Updates already installed platform.
* *
* @param {String} destination Destination directory, where platform installed * @param {String} destination Destination directory, where platform installed
* @param {Object} [options] An options object. The most common options are: * @param {Object} [options] An options object. The most common options are:
* @param {String} [options.customTemplate] A path to custom template, that * @param {String} [options.customTemplate] A path to custom template, that
* should override the default one from platform. * should override the default one from platform.
* @param {Boolean} [options.link] Flag that indicates that platform's * @param {Boolean} [options.link] Flag that indicates that platform's
* sources will be linked to installed platform instead of copying. * sources will be linked to installed platform instead of copying.
* @param {EventEmitter} [events] An EventEmitter instance that will be used for * @param {EventEmitter} [events] An EventEmitter instance that will be used for
* logging purposes. If no EventEmitter provided, all events will be logged to * logging purposes. If no EventEmitter provided, all events will be logged to
* console * console
* *
* @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
* instance or rejected with CordovaError. * instance or rejected with CordovaError.
*/ */
Api.updatePlatform = function (destination, options, events) { Api.updatePlatform = function (destination, options, events) {
return require('../../lib/create') return require('../../lib/create')
.update(destination, options, events || ConsoleLogger.get()) .update(destination, options, events || ConsoleLogger.get())
.then(function (destination) { .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);
}); });
}; };
/** /**
* Gets a CordovaPlatform object, that represents the platform structure. * Gets a CordovaPlatform object, that represents the platform structure.
* *
* @return {CordovaPlatform} A structure that contains the description of * @return {CordovaPlatform} A structure that contains the description of
* platform's file structure and other properties of platform. * platform's file structure and other properties of platform.
*/ */
Api.prototype.getPlatformInfo = function () { Api.prototype.getPlatformInfo = function () {
var result = {}; var result = {};
result.locations = this.locations; result.locations = this.locations;
result.root = this.root; result.root = this.root;
result.name = this.platform; result.name = this.platform;
result.version = require('./version'); result.version = require('./version');
result.projectConfig = this._config; result.projectConfig = this._config;
return result; return result;
}; };
/** /**
* Updates installed platform with provided www assets and new app * Updates installed platform with provided www assets and new app
* configuration. This method is required for CLI workflow and will be called * configuration. This method is required for CLI workflow and will be called
* each time before build, so the changes, made to app configuration and www * each time before build, so the changes, made to app configuration and www
* code, will be applied to platform. * code, will be applied to platform.
* *
* @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a * @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
* project structure and configuration, that should be applied to platform * project structure and configuration, that should be applied to platform
* (contains project's www location and ConfigParser instance for project's * (contains project's www location and ConfigParser instance for project's
* config). * config).
* *
* @return {Promise} Return a promise either fulfilled, or rejected with * @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance. * CordovaError instance.
*/ */
Api.prototype.prepare = function (cordovaProject) { Api.prototype.prepare = function (cordovaProject) {
return require('./lib/prepare').prepare.call(this, cordovaProject); return require('./lib/prepare').prepare.call(this, cordovaProject);
}; };
/** /**
* Installs a new plugin into platform. This method only copies non-www files * Installs a new plugin into platform. This method only copies non-www files
* (sources, libs, etc.) to platform. It also doesn't resolves the * (sources, libs, etc.) to platform. It also doesn't resolves the
* dependencies of plugin. Both of handling of www files, such as assets and * dependencies of plugin. Both of handling of www files, such as assets and
* js-files and resolving dependencies are the responsibility of caller. * js-files and resolving dependencies are the responsibility of caller.
* *
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin * @param {PluginInfo} plugin A PluginInfo instance that represents plugin
* that will be installed. * that will be installed.
* @param {Object} installOptions An options object. Possible options below: * @param {Object} installOptions An options object. Possible options below:
* @param {Boolean} installOptions.link: Flag that specifies that plugin * @param {Boolean} installOptions.link: Flag that specifies that plugin
* sources will be symlinked to app's directory instead of copying (if * sources will be symlinked to app's directory instead of copying (if
* possible). * possible).
* @param {Object} installOptions.variables An object that represents * @param {Object} installOptions.variables An object that represents
* variables that will be used to install plugin. See more details on plugin * variables that will be used to install plugin. See more details on plugin
* variables in documentation: * variables in documentation:
* https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html * https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
* *
* @return {Promise} Return a promise either fulfilled, or rejected with * @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance. * CordovaError instance.
*/ */
Api.prototype.addPlugin = function (plugin, installOptions) { Api.prototype.addPlugin = function (plugin, installOptions) {
if (!plugin || plugin.constructor.name !== 'PluginInfo') if (!plugin || plugin.constructor.name !== 'PluginInfo')
return Q.reject(new CordovaError('The parameter is incorrect. The first parameter to addPlugin should be a PluginInfo instance')); return Q.reject(new CordovaError('The parameter is incorrect. The first parameter to addPlugin should be a PluginInfo instance'));
installOptions = installOptions || {}; installOptions = installOptions || {};
installOptions.variables = installOptions.variables || {}; installOptions.variables = installOptions.variables || {};
var self = this; var self = this;
var actions = new ActionStack(); var actions = new ActionStack();
var project = AndroidProject.getProjectFile(this.root); var project = AndroidProject.getProjectFile(this.root);
// gather all files needs to be handled during install // gather all files needs to be handled during install
plugin.getFilesAndFrameworks(this.platform) plugin.getFilesAndFrameworks(this.platform)
.concat(plugin.getAssets(this.platform)) .concat(plugin.getAssets(this.platform))
.concat(plugin.getJsModules(this.platform)) .concat(plugin.getJsModules(this.platform))
.forEach(function(item) { .forEach(function(item) {
actions.push(actions.createAction( actions.push(actions.createAction(
pluginHandlers.getInstaller(item.itemType), [item, plugin, project, installOptions], pluginHandlers.getInstaller(item.itemType), [item, plugin, project, installOptions],
pluginHandlers.getUninstaller(item.itemType), [item, plugin, project, installOptions])); pluginHandlers.getUninstaller(item.itemType), [item, plugin, project, installOptions]));
}); });
// run through the action stack // run through the action stack
return actions.process(this.platform) return actions.process(this.platform)
.then(function () { .then(function () {
if (project) { if (project) {
project.write(); project.write();
} }
// Add PACKAGE_NAME variable into vars // Add PACKAGE_NAME variable into vars
if (!installOptions.variables.PACKAGE_NAME) { if (!installOptions.variables.PACKAGE_NAME) {
installOptions.variables.PACKAGE_NAME = project.getPackageName(); installOptions.variables.PACKAGE_NAME = project.getPackageName();
} }
self._munger self._munger
// Ignore passed `is_top_level` option since platform itself doesn't know // Ignore passed `is_top_level` option since platform itself doesn't know
// anything about managing dependencies - it's responsibility of caller. // anything about managing dependencies - it's responsibility of caller.
.add_plugin_changes(plugin, installOptions.variables, /*is_top_level=*/true, /*should_increment=*/true) .add_plugin_changes(plugin, installOptions.variables, /*is_top_level=*/true, /*should_increment=*/true)
.save_all(); .save_all();
var targetDir = installOptions.usePlatformWww ? var targetDir = installOptions.usePlatformWww ?
self.locations.platformWww : self.locations.platformWww :
self.locations.www; self.locations.www;
self._addModulesInfo(plugin, targetDir); self._addModulesInfo(plugin, targetDir);
}); });
}; };
/** /**
* Removes an installed plugin from platform. * Removes an installed plugin from platform.
* *
* Since method accepts PluginInfo instance as input parameter instead of plugin * Since method accepts PluginInfo instance as input parameter instead of plugin
* id, caller shoud take care of managing/storing PluginInfo instances for * id, caller shoud take care of managing/storing PluginInfo instances for
* future uninstalls. * future uninstalls.
* *
* @param {PluginInfo} plugin A PluginInfo instance that represents plugin * @param {PluginInfo} plugin A PluginInfo instance that represents plugin
* that will be installed. * that will be installed.
* *
* @return {Promise} Return a promise either fulfilled, or rejected with * @return {Promise} Return a promise either fulfilled, or rejected with
* CordovaError instance. * CordovaError instance.
*/ */
Api.prototype.removePlugin = function (plugin, uninstallOptions) { Api.prototype.removePlugin = function (plugin, uninstallOptions) {
if (!plugin || plugin.constructor.name !== 'PluginInfo') if (!plugin || plugin.constructor.name !== 'PluginInfo')
return Q.reject(new CordovaError('The parameter is incorrect. The first parameter to addPlugin should be a PluginInfo instance')); return Q.reject(new CordovaError('The parameter is incorrect. The first parameter to addPlugin should be a PluginInfo instance'));
var self = this; var self = this;
var actions = new ActionStack(); var actions = new ActionStack();
var project = AndroidProject.getProjectFile(this.root); var project = AndroidProject.getProjectFile(this.root);
// queue up plugin files // queue up plugin files
plugin.getFilesAndFrameworks(this.platform) plugin.getFilesAndFrameworks(this.platform)
.concat(plugin.getAssets(this.platform)) .concat(plugin.getAssets(this.platform))
.concat(plugin.getJsModules(this.platform)) .concat(plugin.getJsModules(this.platform))
.forEach(function(item) { .forEach(function(item) {
actions.push(actions.createAction( actions.push(actions.createAction(
pluginHandlers.getUninstaller(item.itemType), [item, plugin, project, uninstallOptions], pluginHandlers.getUninstaller(item.itemType), [item, plugin, project, uninstallOptions],
pluginHandlers.getInstaller(item.itemType), [item, plugin, project, uninstallOptions])); pluginHandlers.getInstaller(item.itemType), [item, plugin, project, uninstallOptions]));
}); });
// run through the action stack // run through the action stack
return actions.process(this.platform) return actions.process(this.platform)
.then(function() { .then(function() {
if (project) { if (project) {
project.write(); project.write();
} }
self._munger self._munger
// Ignore passed `is_top_level` option since platform itself doesn't know // Ignore passed `is_top_level` option since platform itself doesn't know
// anything about managing dependencies - it's responsibility of caller. // anything about managing dependencies - it's responsibility of caller.
.remove_plugin_changes(plugin, /*is_top_level=*/true) .remove_plugin_changes(plugin, /*is_top_level=*/true)
.save_all(); .save_all();
var targetDir = uninstallOptions.usePlatformWww ? var targetDir = uninstallOptions.usePlatformWww ?
self.locations.platformWww : self.locations.platformWww :
self.locations.www; self.locations.www;
self._removeModulesInfo(plugin, targetDir); self._removeModulesInfo(plugin, targetDir);
}); });
}; };
/** /**
* Builds an application package for current platform. * Builds an application package for current platform.
* *
* @param {Object} buildOptions A build options. This object's structure is * @param {Object} buildOptions A build options. This object's structure is
* highly depends on platform's specific. The most common options are: * highly depends on platform's specific. The most common options are:
* @param {Boolean} buildOptions.debug Indicates that packages should be * @param {Boolean} buildOptions.debug Indicates that packages should be
* built with debug configuration. This is set to true by default unless the * built with debug configuration. This is set to true by default unless the
* 'release' option is not specified. * 'release' option is not specified.
* @param {Boolean} buildOptions.release Indicates that packages should be * @param {Boolean} buildOptions.release Indicates that packages should be
* built with release configuration. If not set to true, debug configuration * built with release configuration. If not set to true, debug configuration
* will be used. * will be used.
* @param {Boolean} buildOptions.device Specifies that built app is intended * @param {Boolean} buildOptions.device Specifies that built app is intended
* to run on device * to run on device
* @param {Boolean} buildOptions.emulator: Specifies that built app is * @param {Boolean} buildOptions.emulator: Specifies that built app is
* intended to run on emulator * intended to run on emulator
* @param {String} buildOptions.target Specifies the device id that will be * @param {String} buildOptions.target Specifies the device id that will be
* used to run built application. * used to run built application.
* @param {Boolean} buildOptions.nobuild Indicates that this should be a * @param {Boolean} buildOptions.nobuild Indicates that this should be a
* dry-run call, so no build artifacts will be produced. * dry-run call, so no build artifacts will be produced.
* @param {String[]} buildOptions.archs Specifies chip architectures which * @param {String[]} buildOptions.archs Specifies chip architectures which
* app packages should be built for. List of valid architectures is depends on * app packages should be built for. List of valid architectures is depends on
* platform. * platform.
* @param {String} buildOptions.buildConfig The path to build configuration * @param {String} buildOptions.buildConfig The path to build configuration
* file. The format of this file is depends on platform. * file. The format of this file is depends on platform.
* @param {String[]} buildOptions.argv Raw array of command-line arguments, * @param {String[]} buildOptions.argv Raw array of command-line arguments,
* passed to `build` command. The purpose of this property is to pass a * passed to `build` command. The purpose of this property is to pass a
* platform-specific arguments, and eventually let platform define own * platform-specific arguments, and eventually let platform define own
* arguments processing logic. * arguments processing logic.
* *
* @return {Promise<Object[]>} A promise either fulfilled with an array of build * @return {Promise<Object[]>} A promise either fulfilled with an array of build
* artifacts (application packages) if package was built successfully, * artifacts (application packages) if package was built successfully,
* or rejected with CordovaError. The resultant build artifact objects is not * or rejected with CordovaError. The resultant build artifact objects is not
* strictly typed and may conatin arbitrary set of fields as in sample below. * strictly typed and may conatin arbitrary set of fields as in sample below.
* *
* { * {
* architecture: 'x86', * architecture: 'x86',
* buildType: 'debug', * buildType: 'debug',
* path: '/path/to/build', * path: '/path/to/build',
* type: 'app' * type: 'app'
* } * }
* *
* The return value in most cases will contain only one item but in some cases * The return value in most cases will contain only one item but in some cases
* there could be multiple items in output array, e.g. when multiple * there could be multiple items in output array, e.g. when multiple
* arhcitectures is specified. * arhcitectures is specified.
*/ */
Api.prototype.build = function (buildOptions) { Api.prototype.build = function (buildOptions) {
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').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 {
buildType: buildResults.buildType, buildType: buildResults.buildType,
buildMethod: buildResults.buildMethod, buildMethod: buildResults.buildMethod,
path: apkPath, path: apkPath,
type: 'apk' type: 'apk'
}; };
}); });
}); });
}; };
/** /**
* Builds an application package for current platform and runs it on * Builds an application package for current platform and runs it on
* specified/default device. If no 'device'/'emulator'/'target' options are * specified/default device. If no 'device'/'emulator'/'target' options are
* specified, then tries to run app on default device if connected, otherwise * specified, then tries to run app on default device if connected, otherwise
* runs the app on emulator. * runs the app on emulator.
* *
* @param {Object} runOptions An options object. The structure is the same * @param {Object} runOptions An options object. The structure is the same
* as for build options. * as for build options.
* *
* @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);
}); });
}; };
/** /**
* Cleans out the build artifacts from platform's directory. * Cleans out the build artifacts from platform's directory.
* *
* @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);
}); });
}; };
/** /**
* 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
* built successfully. * built successfully.
* *
* @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();
}; };
module.exports = Api; module.exports = Api;
/** /**
* Removes the specified modules from list of installed modules and updates * Removes the specified modules from list of installed modules and updates
* platform_json and cordova_plugins.js on disk. * platform_json and cordova_plugins.js on disk.
* *
* @param {PluginInfo} plugin PluginInfo instance for plugin, which modules * @param {PluginInfo} plugin PluginInfo instance for plugin, which modules
* needs to be added. * needs to be added.
* @param {String} targetDir The directory, where updated cordova_plugins.js * @param {String} targetDir The directory, where updated cordova_plugins.js
* should be written to. * should be written to.
*/ */
Api.prototype._addModulesInfo = function(plugin, targetDir) { Api.prototype._addModulesInfo = function(plugin, targetDir) {
var installedModules = this._platformJson.root.modules || []; var installedModules = this._platformJson.root.modules || [];
var installedPaths = installedModules.map(function (installedModule) { var installedPaths = installedModules.map(function (installedModule) {
return installedModule.file; return installedModule.file;
}); });
var modulesToInstall = plugin.getJsModules(this.platform) var modulesToInstall = plugin.getJsModules(this.platform)
.filter(function (moduleToInstall) { .filter(function (moduleToInstall) {
return installedPaths.indexOf(moduleToInstall.file) === -1; return installedPaths.indexOf(moduleToInstall.file) === -1;
}).map(function (moduleToInstall) { }).map(function (moduleToInstall) {
var moduleName = plugin.id + '.' + ( moduleToInstall.name || moduleToInstall.src.match(/([^\/]+)\.js/)[1] ); var moduleName = plugin.id + '.' + ( moduleToInstall.name || moduleToInstall.src.match(/([^\/]+)\.js/)[1] );
var obj = { var obj = {
file: ['plugins', plugin.id, moduleToInstall.src].join('/'), file: ['plugins', plugin.id, moduleToInstall.src].join('/'),
id: moduleName id: moduleName
}; };
if (moduleToInstall.clobbers.length > 0) { if (moduleToInstall.clobbers.length > 0) {
obj.clobbers = moduleToInstall.clobbers.map(function(o) { return o.target; }); obj.clobbers = moduleToInstall.clobbers.map(function(o) { return o.target; });
} }
if (moduleToInstall.merges.length > 0) { if (moduleToInstall.merges.length > 0) {
obj.merges = moduleToInstall.merges.map(function(o) { return o.target; }); obj.merges = moduleToInstall.merges.map(function(o) { return o.target; });
} }
if (moduleToInstall.runs) { if (moduleToInstall.runs) {
obj.runs = true; obj.runs = true;
} }
return obj; return obj;
}); });
this._platformJson.root.modules = installedModules.concat(modulesToInstall); this._platformJson.root.modules = installedModules.concat(modulesToInstall);
this._writePluginModules(targetDir); this._writePluginModules(targetDir);
this._platformJson.save(); this._platformJson.save();
}; };
/** /**
* Removes the specified modules from list of installed modules and updates * Removes the specified modules from list of installed modules and updates
* platform_json and cordova_plugins.js on disk. * platform_json and cordova_plugins.js on disk.
* *
* @param {PluginInfo} plugin PluginInfo instance for plugin, which modules * @param {PluginInfo} plugin PluginInfo instance for plugin, which modules
* needs to be removed. * needs to be removed.
* @param {String} targetDir The directory, where updated cordova_plugins.js * @param {String} targetDir The directory, where updated cordova_plugins.js
* should be written to. * should be written to.
*/ */
Api.prototype._removeModulesInfo = function(plugin, targetDir) { Api.prototype._removeModulesInfo = function(plugin, targetDir) {
var installedModules = this._platformJson.root.modules || []; var installedModules = this._platformJson.root.modules || [];
var modulesToRemove = plugin.getJsModules(this.platform) var modulesToRemove = plugin.getJsModules(this.platform)
.map(function (jsModule) { .map(function (jsModule) {
return ['plugins', plugin.id, jsModule.src].join('/'); return ['plugins', plugin.id, jsModule.src].join('/');
}); });
var updatedModules = installedModules var updatedModules = installedModules
.filter(function (installedModule) { .filter(function (installedModule) {
return (modulesToRemove.indexOf(installedModule.file) === -1); return (modulesToRemove.indexOf(installedModule.file) === -1);
}); });
this._platformJson.root.modules = updatedModules; this._platformJson.root.modules = updatedModules;
this._writePluginModules(targetDir); this._writePluginModules(targetDir);
this._platformJson.save(); this._platformJson.save();
}; };
/** /**
* Fetches all installed modules, generates cordova_plugins contents and writes * Fetches all installed modules, generates cordova_plugins contents and writes
* it to file. * it to file.
* *
* @param {String} targetDir Directory, where write cordova_plugins.js to. * @param {String} targetDir Directory, where write cordova_plugins.js to.
* Ususally it is either <platform>/www or <platform>/platform_www * Ususally it is either <platform>/www or <platform>/platform_www
* directories. * directories.
*/ */
Api.prototype._writePluginModules = function (targetDir) { Api.prototype._writePluginModules = function (targetDir) {
var self = this; var self = this;
// Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js
var final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n'; var final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n';
final_contents += 'module.exports = ' + JSON.stringify(this._platformJson.root.modules, null, ' ') + ';\n'; final_contents += 'module.exports = ' + JSON.stringify(this._platformJson.root.modules, null, ' ') + ';\n';
final_contents += 'module.exports.metadata = \n'; final_contents += 'module.exports.metadata = \n';
final_contents += '// TOP OF METADATA\n'; final_contents += '// TOP OF METADATA\n';
var pluginMetadata = Object.keys(this._platformJson.root.installed_plugins) var pluginMetadata = Object.keys(this._platformJson.root.installed_plugins)
.reduce(function (metadata, plugin) { .reduce(function (metadata, plugin) {
metadata[plugin] = self._platformJson.root.installed_plugins[plugin].version; metadata[plugin] = self._platformJson.root.installed_plugins[plugin].version;
return metadata; return metadata;
}, {}); }, {});
final_contents += JSON.stringify(pluginMetadata, null, 4) + '\n'; final_contents += JSON.stringify(pluginMetadata, null, 4) + '\n';
final_contents += '// BOTTOM OF METADATA\n'; final_contents += '// BOTTOM OF METADATA\n';
final_contents += '});'; // Close cordova.define. final_contents += '});'; // Close cordova.define.
shell.mkdir('-p', targetDir); shell.mkdir('-p', targetDir);
fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8'); fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8');
}; };

View File

@ -1,78 +1,78 @@
var Q = require('q'); var Q = require('q');
var os = require('os'); var os = require('os');
var events = require('cordova-common').events; 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;
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/);
} }
/** /**
* Lists available/connected devices and emulators * Lists available/connected devices and emulators
* *
* @param {Object} opts Various options * @param {Object} opts Various options
* @param {Boolean} opts.emulators Specifies whether this method returns * @param {Boolean} opts.emulators Specifies whether this method returns
* emulators only * emulators only
* *
* @return {Promise<String[]>} list of available/connected * @return {Promise<String[]>} list of available/connected
* 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);
}).map(function (line) { }).map(function (line) {
return line.replace(/\tdevice/, '').replace('\r', ''); return line.replace(/\tdevice/, '').replace('\r', '');
}); });
}); });
}; };
Adb.install = function (target, packagePath, opts) { Adb.install = function (target, packagePath, opts) {
events.emit('verbose', 'Installing apk ' + packagePath + ' on ' + target + '...'); events.emit('verbose', 'Installing apk ' + packagePath + ' on ' + 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/))
return Q.reject(new CordovaError('Failed to install apk to device: ' + output)); return Q.reject(new CordovaError('Failed to install apk to device: ' + output));
}); });
}; };
Adb.uninstall = function (target, packageId) { Adb.uninstall = function (target, packageId) {
events.emit('verbose', 'Uninstalling ' + packageId + ' from ' + target + '...'); events.emit('verbose', 'Uninstalling ' + packageId + ' from ' + target + '...');
return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()}); return spawn('adb', ['-s', target, 'uninstall', packageId], {cwd: os.tmpdir()});
}; };
Adb.shell = function (target, shellCommand) { Adb.shell = function (target, shellCommand) {
events.emit('verbose', 'Running command "' + shellCommand + '" on ' + target + '...'); events.emit('verbose', 'Running command "' + shellCommand + '" on ' + 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));
}); });
}; };
Adb.start = function (target, activityName) { Adb.start = function (target, activityName) {
events.emit('verbose', 'Starting application "' + activityName + '" on ' + target + '...'); events.emit('verbose', 'Starting application "' + activityName + '" on ' + 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));
}); });
}; };
module.exports = Adb; module.exports = Adb;

View File

@ -1,161 +1,161 @@
/** /**
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
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') {
throw new Error(path + ' has incorrect root node name (expected "manifest")'); throw new Error(path + ' has incorrect root node name (expected "manifest")');
} }
} }
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 () {
return activity.attrib['android:name']; return activity.attrib['android:name'];
}, },
setName: function (name) { setName: function (name) {
if (!name) { if (!name) {
delete activity.attrib['android:name']; delete activity.attrib['android:name'];
} else { } else {
activity.attrib['android:name'] = name; activity.attrib['android:name'] = name;
} }
return this; return this;
}, },
getOrientation: function () { getOrientation: function () {
return activity.attrib['android:screenOrientation']; return activity.attrib['android:screenOrientation'];
}, },
setOrientation: function (orientation) { setOrientation: function (orientation) {
if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) { if (!orientation || orientation.toLowerCase() === DEFAULT_ORIENTATION) {
delete activity.attrib['android:screenOrientation']; delete activity.attrib['android:screenOrientation'];
} else { } else {
activity.attrib['android:screenOrientation'] = orientation; activity.attrib['android:screenOrientation'] = orientation;
} }
return this; return this;
}, },
getLaunchMode: function () { getLaunchMode: function () {
return activity.attrib['android:launchMode']; return activity.attrib['android:launchMode'];
}, },
setLaunchMode: function (launchMode) { setLaunchMode: function (launchMode) {
if (!launchMode) { if (!launchMode) {
delete activity.attrib['android:launchMode']; delete activity.attrib['android:launchMode'];
} else { } else {
activity.attrib['android:launchMode'] = launchMode; activity.attrib['android:launchMode'] = launchMode;
} }
return this; return this;
} }
}; };
}; };
['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
usesSdk = new et.Element('uses-sdk'); usesSdk = new et.Element('uses-sdk');
this.doc.getroot().append(usesSdk); this.doc.getroot().append(usesSdk);
} }
if (prefValue) { if (prefValue) {
usesSdk.attrib['android:' + prefName] = prefValue; usesSdk.attrib['android:' + prefName] = prefValue;
} }
return this; return this;
}; };
}); });
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';
} else { } else {
// The default value is "false", so we can remove attribute at all. // The default value is "false", so we can remove attribute at all.
delete application.attrib['android:debuggable']; delete application.attrib['android:debuggable'];
} }
return this; return this;
}; };
/** /**
* Writes manifest to disk syncronously. If filename is specified, then manifest * Writes manifest to disk syncronously. If filename is specified, then manifest
* will be written to that file * will be written to that file
* *
* @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');
}; };
module.exports = AndroidManifest; module.exports = AndroidManifest;
function capitalize (str) { function capitalize (str) {
return str.charAt(0).toUpperCase() + str.slice(1); return str.charAt(0).toUpperCase() + str.slice(1);
} }

View File

@ -1,184 +1,184 @@
/** /**
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var properties_parser = require('properties-parser'); var properties_parser = require('properties-parser');
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
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))) {
if (currentValue === value) { if (currentValue === value) {
while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) { while ((currentValue = projectProperties.get(key + '.' + (i + 1)))) {
projectProperties.set(key + '.' + i, currentValue); projectProperties.set(key + '.' + i, currentValue);
i++; i++;
} }
projectProperties.set(key + '.' + i); projectProperties.set(key + '.' + i);
break; break;
} }
i++; i++;
} }
projectProperties.dirty = true; projectProperties.dirty = true;
} }
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');
} }
AndroidProject.getProjectFile = function (projectDir) { AndroidProject.getProjectFile = function (projectDir) {
if (!projectFileCache[projectDir]) { if (!projectFileCache[projectDir]) {
projectFileCache[projectDir] = new AndroidProject(projectDir); projectFileCache[projectDir] = new AndroidProject(projectDir);
} }
return projectFileCache[projectDir]; return projectFileCache[projectDir];
}; };
AndroidProject.purgeCache = function (projectDir) { AndroidProject.purgeCache = function (projectDir) {
if (projectDir) { if (projectDir) {
delete projectFileCache[projectDir]; delete projectFileCache[projectDir];
} else { } else {
projectFileCache = {}; projectFileCache = {};
} }
}; };
/** /**
* Reads the package name out of the Android Manifest file * Reads the package name out of the Android Manifest file
* *
* @param {String} projectDir The absolute path to the directory containing the project * @param {String} projectDir The absolute path to the directory containing the project
* *
* @return {String} The name of the package * @return {String} The name of the package
*/ */
AndroidProject.prototype.getPackageName = function() { AndroidProject.prototype.getPackageName = function() {
return new AndroidManifest(path.join(this.projectDir, 'AndroidManifest.xml')).getPackageId(); return new AndroidManifest(path.join(this.projectDir, 'AndroidManifest.xml')).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();
var lastDotIndex = packageName.lastIndexOf('.'); var lastDotIndex = packageName.lastIndexOf('.');
var prefix = packageName.substring(lastDotIndex + 1); var prefix = packageName.substring(lastDotIndex + 1);
var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src)); var subRelativeDir = path.join(plugin_id, prefix + '-' + path.basename(src));
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);
// TODO: Setting the target needs to happen only for pre-3.7.0 projects // TODO: Setting the target needs to happen only for pre-3.7.0 projects
if (fs.existsSync(subProjectFile)) { if (fs.existsSync(subProjectFile)) {
var subProperties = this._getPropertiesFile(subProjectFile); var subProperties = this._getPropertiesFile(subProjectFile);
subProperties.set('target', parentProperties.get('target')); subProperties.set('target', parentProperties.get('target'));
subProperties.dirty = true; subProperties.dirty = true;
this._subProjectDirs[subDir] = true; this._subProjectDirs[subDir] = true;
} }
addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(parentDir, subDir)); addToPropertyList(parentProperties, 'android.library.reference', getRelativeLibraryPath(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));
delete this._subProjectDirs[subDir]; delete this._subProjectDirs[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;
} }
this._dirty = false; this._dirty = false;
for (var filename in this._propertiesEditors) { for (var filename in this._propertiesEditors) {
var editor = this._propertiesEditors[filename]; var editor = this._propertiesEditors[filename];
if (editor.dirty) { if (editor.dirty) {
fs.writeFileSync(filename, editor.toString()); fs.writeFileSync(filename, editor.toString());
editor.dirty = false; editor.dirty = false;
} }
} }
}; };
AndroidProject.prototype._getPropertiesFile = function (filename) { AndroidProject.prototype._getPropertiesFile = function (filename) {
if (!this._propertiesEditors[filename]) { if (!this._propertiesEditors[filename]) {
if (fs.existsSync(filename)) { if (fs.existsSync(filename)) {
this._propertiesEditors[filename] = properties_parser.createEditor(filename); this._propertiesEditors[filename] = properties_parser.createEditor(filename);
} else { } else {
this._propertiesEditors[filename] = properties_parser.createEditor(); this._propertiesEditors[filename] = properties_parser.createEditor();
} }
} }
return this._propertiesEditors[filename]; return this._propertiesEditors[filename];
}; };
module.exports = AndroidProject; module.exports = AndroidProject;

View File

@ -1,75 +1,75 @@
/** /**
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var loggerInstance; var loggerInstance;
var util = require('util'); var util = require('util');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
/** /**
* @class ConsoleLogger * @class ConsoleLogger
* @extends EventEmitter * @extends EventEmitter
* *
* Implementing basic logging for platform. Inherits regular NodeJS * Implementing basic logging for platform. Inherits regular NodeJS
* EventEmitter. All events, emitted on this class instance are immediately * EventEmitter. All events, emitted on this class instance are immediately
* logged to console. * logged to console.
* *
* Also attaches handler to process' uncaught exceptions, so these exceptions * Also attaches handler to process' uncaught exceptions, so these exceptions
* logged to console similar to regular error events. * logged to console similar to regular error events.
*/ */
function ConsoleLogger() { function ConsoleLogger() {
EventEmitter.call(this); EventEmitter.call(this);
var isVerbose = process.argv.indexOf('-d') >= 0 || process.argv.indexOf('--verbose') >= 0; var isVerbose = process.argv.indexOf('-d') >= 0 || process.argv.indexOf('--verbose') >= 0;
// For CordovaError print only the message without stack trace unless we // For CordovaError print only the message without stack trace unless we
// are in a verbose mode. // are in a verbose mode.
process.on('uncaughtException', function(err){ process.on('uncaughtException', function(err){
if ((err instanceof CordovaError) && isVerbose) { if ((err instanceof CordovaError) && isVerbose) {
console.error(err.stack); console.error(err.stack);
} else { } else {
console.error(err.message); console.error(err.message);
} }
process.exit(1); process.exit(1);
}); });
this.on('results', console.log); this.on('results', console.log);
this.on('verbose', function () { this.on('verbose', function () {
if (isVerbose) if (isVerbose)
console.log.apply(console, arguments); console.log.apply(console, arguments);
}); });
this.on('info', console.log); this.on('info', console.log);
this.on('log', console.log); this.on('log', console.log);
this.on('warn', console.warn); this.on('warn', console.warn);
} }
util.inherits(ConsoleLogger, EventEmitter); util.inherits(ConsoleLogger, EventEmitter);
/** /**
* Returns already instantiated/newly created instance of ConsoleLogger class. * Returns already instantiated/newly created instance of ConsoleLogger class.
* This method should be used instead of creating ConsoleLogger directly, * This method should be used instead of creating ConsoleLogger directly,
* otherwise we'll get multiple handlers attached to process' * otherwise we'll get multiple handlers attached to process'
* uncaughtException * uncaughtException
* *
* @return {ConsoleLogger} New or already created instance of ConsoleLogger * @return {ConsoleLogger} New or already created instance of ConsoleLogger
*/ */
ConsoleLogger.get = function () { ConsoleLogger.get = function () {
loggerInstance = loggerInstance || new ConsoleLogger(); loggerInstance = loggerInstance || new ConsoleLogger();
return loggerInstance; return loggerInstance;
}; };
module.exports = ConsoleLogger; module.exports = ConsoleLogger;

View File

@ -1,301 +1,301 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var Q = require('q'), var Q = require('q'),
path = require('path'), path = require('path'),
fs = require('fs'), fs = require('fs'),
nopt = require('nopt'); nopt = require('nopt');
var Adb = require('./Adb'); var Adb = require('./Adb');
var builders = require('./builders/builders'); var builders = require('./builders/builders');
var events = require('cordova-common').events; 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) { function parseOpts(options, resolvedTarget) {
options = options || {}; options = options || {};
options.argv = nopt({ options.argv = nopt({
gradle: Boolean, gradle: Boolean,
ant: Boolean, ant: Boolean,
prepenv: Boolean, prepenv: Boolean,
versionCode: String, versionCode: String,
minSdkVersion: String, minSdkVersion: String,
gradleArg: String, gradleArg: String,
keystore: path, keystore: path,
alias: String, alias: String,
storePassword: String, storePassword: String,
password: String, password: String,
keystoreType: String keystoreType: String
}, {}, options.argv, 0); }, {}, options.argv, 0);
var ret = { var ret = {
buildType: options.release ? 'release' : 'debug', buildType: options.release ? 'release' : 'debug',
buildMethod: process.env.ANDROID_BUILD || 'gradle', buildMethod: process.env.ANDROID_BUILD || 'gradle',
prepEnv: options.argv.prepenv, prepEnv: options.argv.prepenv,
arch: resolvedTarget && resolvedTarget.arch, arch: resolvedTarget && resolvedTarget.arch,
extraArgs: [] extraArgs: []
}; };
if (options.argv.ant || options.argv.gradle) if (options.argv.ant || options.argv.gradle)
ret.buildMethod = options.argv.ant ? 'ant' : 'gradle'; ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
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.versionCode); ret.extraArgs.push('-PcdvVersionCode=' + options.versionCode);
if (options.argv.minSdkVersion) if (options.argv.minSdkVersion)
ret.extraArgs.push('-PcdvMinSdkVersion=' + options.minSdkVersion); ret.extraArgs.push('-PcdvMinSdkVersion=' + options.minSdkVersion);
if (options.argv.gradleArg) if (options.argv.gradleArg)
ret.extraArgs.push(options.gradleArg); ret.extraArgs.push(options.gradleArg);
var packageArgs = {}; var packageArgs = {};
if (options.argv.keystore) if (options.argv.keystore)
packageArgs.keystore = path.relative(this.root, path.resolve(options.argv.keystore)); packageArgs.keystore = path.relative(this.root, 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;
// If some values are not specified as command line arguments - use build config to supplement them. // If some values are not specified as command line arguments - use build config to supplement them.
// Command line arguemnts have precedence over build config. // Command line arguemnts have precedence over build config.
if (buildConfig) { if (buildConfig) {
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 config = JSON.parse(fs.readFileSync(buildConfig, 'utf8')); var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
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) {
packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore); packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.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];
}); });
} }
} }
if (packageArgs.keystore && packageArgs.alias) { if (packageArgs.keystore && packageArgs.alias) {
ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword, ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
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.');
} }
} }
return ret; return ret;
} }
/* /*
* 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); var opts = parseOpts(options);
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);
}); });
}; };
/** /**
* Builds the project with the specifed options. * Builds the project with the specifed options.
* *
* @param {BuildOptions} options A set of options. See PlatformApi.build * @param {BuildOptions} options A set of options. See PlatformApi.build
* method documentation for reference. * method documentation for reference.
* @param {Object} optResolvedTarget A deployment target. Used to pass * @param {Object} optResolvedTarget A deployment target. Used to pass
* target architecture from upstream 'run' call. TODO: remove this option in * target architecture from upstream 'run' call. TODO: remove this option in
* favor of setting buildOptions.archs field. * favor of setting buildOptions.archs field.
* *
* @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); var opts = parseOpts(options, optResolvedTarget);
var builder = builders.getBuilder(opts.buildMethod); var builder = builders.getBuilder(opts.buildMethod);
var self = this; var self = this;
return builder.prepEnv(opts) return builder.prepEnv(opts)
.then(function() { .then(function() {
if (opts.prepEnv) { if (opts.prepEnv) {
self.events.emit('verbose', 'Build file successfully prepared.'); self.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);
self.events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t')); self.events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
return { return {
apkPaths: apkPaths, apkPaths: apkPaths,
buildType: opts.buildType, buildType: opts.buildType,
buildMethod: opts.buildMethod buildMethod: opts.buildMethod
}; };
}); });
}); });
}; };
// Called by plugman after installing plugins, and by create script after creating project. // Called by plugman after installing plugins, and by create script after creating project.
module.exports.prepBuildFiles = function() { module.exports.prepBuildFiles = function() {
return builders.getBuilder('gradle').prepBuildFiles(); return builders.getBuilder('gradle').prepBuildFiles();
}; };
/* /*
* 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.')) .timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
.then(null, function(err) { .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.
return spawn('killall', ['adb']) return spawn('killall', ['adb'])
.then(function() { .then(function() {
events.emit('verbose', 'adb seems hung. retrying.'); events.emit('verbose', 'adb seems hung. retrying.');
return helper() return helper()
.then(null, function() { .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', 'Now device not found... restarting adb again.'); events.emit('warn', 'Now device not found... restarting adb again.');
return spawn('killall', ['adb']) return spawn('killall', ['adb'])
.then(function() { .then(function() {
return helper() return helper()
.then(null, function() { .then(null, function() {
return Q.reject(new CordovaError('USB is flakey. Try unplugging & replugging the device.')); return Q.reject(new CordovaError('USB is flakey. 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);
}); });
} }
throw err; throw err;
}); });
}; };
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);
}); });
var archPattern = new RegExp('-' + arch); var archPattern = new RegExp('-' + arch);
var hasArchPattern = /-x86|-arm/; var hasArchPattern = /-x86|-arm/;
for (var i = 0; i < paths.length; ++i) { for (var i = 0; i < paths.length; ++i) {
var apkName = path.basename(paths[i]); var apkName = path.basename(paths[i]);
if (hasArchPattern.exec(apkName)) { if (hasArchPattern.exec(apkName)) {
if (archPattern.exec(apkName)) { if (archPattern.exec(apkName)) {
return paths[i]; return paths[i];
} }
} else { } else {
return paths[i]; return paths[i];
} }
} }
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
}; };
this.alias = { this.alias = {
'name': 'key.alias', 'name': 'key.alias',
'value': alias 'value': alias
}; };
if (storePassword) { if (storePassword) {
this.storePassword = { this.storePassword = {
'name': 'key.store.password', 'name': 'key.store.password',
'value': storePassword 'value': storePassword
}; };
} }
if (password) { if (password) {
this.password = { this.password = {
'name': 'key.alias.password', 'name': 'key.alias.password',
'value': password 'value': password
}; };
} }
if (keystoreType) { if (keystoreType) {
this.keystoreType = { this.keystoreType = {
'name': 'key.store.type', 'name': 'key.store.type',
'value': keystoreType 'value': 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, '\\\\');
result += '\n'; result += '\n';
}); });
return result; return result;
} }
}; };
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)');
console.log(' \'--release\': will build project for release'); console.log(' \'--release\': will build project for release');
console.log(' \'--ant\': will build project with ant'); console.log(' \'--ant\': will build project with ant');
console.log(' \'--gradle\': will build project with gradle (default)'); console.log(' \'--gradle\': will build project with gradle (default)');
console.log(' \'--nobuild\': will skip build process (useful when using run command)'); console.log(' \'--nobuild\': will skip build process (useful when using run command)');
console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary'); console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary');
console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.'); console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.'); console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true'); console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
console.log(''); console.log('');
console.log('Signed APK flags (overwrites debug/release-signing.proprties) :'); console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)'); console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
console.log(' \'--alias=\': Alias for the key store. (Required)'); console.log(' \'--alias=\': Alias for the key store. (Required)');
console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)'); console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)');
console.log(' \'--password=\': Password for the key. (Optional - prompted)'); console.log(' \'--password=\': Password for the key. (Optional - prompted)');
console.log(' \'--keystoreType\': Type of the keystore. (Optional)'); console.log(' \'--keystoreType\': Type of the keystore. (Optional)');
process.exit(0); process.exit(0);
}; };

View File

@ -1,141 +1,141 @@
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var Q = require('q'); var Q = require('q');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var util = require('util'); var util = require('util');
var shell = require('shelljs'); var shell = require('shelljs');
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;
var check_reqs = require('../check_reqs'); var check_reqs = require('../check_reqs');
var SIGNING_PROPERTIES = '-signing.properties'; var SIGNING_PROPERTIES = '-signing.properties';
var MARKER = 'YOUR CHANGES WILL BE ERASED!'; var MARKER = 'YOUR CHANGES WILL BE ERASED!';
var TEMPLATE = var TEMPLATE =
'# This file is automatically generated.\n' + '# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n'; '# Do not modify this file -- ' + MARKER + '\n';
var GenericBuilder = require('./GenericBuilder'); var GenericBuilder = require('./GenericBuilder');
function AntBuilder (projectRoot) { function AntBuilder (projectRoot) {
GenericBuilder.call(this, projectRoot); GenericBuilder.call(this, projectRoot);
this.binDirs = {ant: this.binDirs.ant}; this.binDirs = {ant: this.binDirs.ant};
} }
util.inherits(AntBuilder, GenericBuilder); util.inherits(AntBuilder, GenericBuilder);
AntBuilder.prototype.getArgs = function(cmd, opts) { AntBuilder.prototype.getArgs = function(cmd, opts) {
var args = [cmd, '-f', path.join(this.root, 'build.xml')]; var args = [cmd, '-f', path.join(this.root, 'build.xml')];
// custom_rules.xml is required for incremental builds. // custom_rules.xml is required for incremental builds.
if (hasCustomRules()) { if (hasCustomRules()) {
args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen'); args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
} }
if(opts.packageInfo) { if(opts.packageInfo) {
args.push('-propertyfile=' + path.join(this.root, opts.buildType + SIGNING_PROPERTIES)); args.push('-propertyfile=' + path.join(this.root, opts.buildType + SIGNING_PROPERTIES));
} }
return args; return args;
}; };
AntBuilder.prototype.prepEnv = function(opts) { AntBuilder.prototype.prepEnv = function(opts) {
var self = this; var self = this;
return check_reqs.check_ant() return check_reqs.check_ant()
.then(function() { .then(function() {
// Copy in build.xml on each build so that: // Copy in build.xml on each build so that:
// A) we don't require the Android SDK at project creation time, and // A) we don't require the Android SDK at project creation time, and
// B) we always use the SDK's latest version of it. // B) we always use the SDK's latest version of it.
/*jshint -W069 */ /*jshint -W069 */
var sdkDir = process.env['ANDROID_HOME']; var sdkDir = process.env['ANDROID_HOME'];
/*jshint +W069 */ /*jshint +W069 */
var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8'); var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
function writeBuildXml(projectPath) { function writeBuildXml(projectPath) {
var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest()); var newData = buildTemplate.replace('PROJECT_NAME', self.extractRealProjectNameFromManifest());
fs.writeFileSync(path.join(projectPath, 'build.xml'), newData); fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
if (!fs.existsSync(path.join(projectPath, 'local.properties'))) { if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE); fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
} }
} }
writeBuildXml(self.root); writeBuildXml(self.root);
var propertiesObj = self.readProjectProperties(); var propertiesObj = self.readProjectProperties();
var subProjects = propertiesObj.libs; var subProjects = propertiesObj.libs;
for (var i = 0; i < subProjects.length; ++i) { for (var i = 0; i < subProjects.length; ++i) {
writeBuildXml(path.join(self.root, subProjects[i])); writeBuildXml(path.join(self.root, subProjects[i]));
} }
if (propertiesObj.systemLibs.length > 0) { if (propertiesObj.systemLibs.length > 0) {
throw new CordovaError('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.'); throw new CordovaError('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.');
} }
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);
if (opts.packageInfo) { if (opts.packageInfo) {
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties()); fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
} else if(isAutoGenerated(propertiesFilePath)) { } else if(isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath); shell.rm('-f', propertiesFilePath);
} }
}); });
}; };
/* /*
* Builds the project with ant. * Builds the project with ant.
* Returns a promise. * Returns a promise.
*/ */
AntBuilder.prototype.build = function(opts) { AntBuilder.prototype.build = function(opts) {
// Without our custom_rules.xml, we need to clean before building. // Without our custom_rules.xml, we need to clean before building.
var ret = Q(); var ret = Q();
if (!hasCustomRules()) { if (!hasCustomRules()) {
// clean will call check_ant() for us. // clean will call check_ant() for us.
ret = this.clean(opts); ret = this.clean(opts);
} }
var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts); var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
return check_reqs.check_ant() return check_reqs.check_ant()
.then(function() { .then(function() {
return spawn('ant', args, {stdio: 'inherit'}); return spawn('ant', args, {stdio: 'inherit'});
}); });
}; };
AntBuilder.prototype.clean = function(opts) { AntBuilder.prototype.clean = function(opts) {
var args = this.getArgs('clean', opts); var args = this.getArgs('clean', opts);
var self = this; var self = this;
return check_reqs.check_ant() return check_reqs.check_ant()
.then(function() { .then(function() {
return spawn('ant', args, {stdio: 'inherit'}); return spawn('ant', args, {stdio: 'inherit'});
}) })
.then(function () { .then(function () {
shell.rm('-rf', path.join(self.root, 'out')); shell.rm('-rf', path.join(self.root, 'out'));
['debug', 'release'].forEach(function(config) { ['debug', 'release'].forEach(function(config) {
var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES); var propertiesFilePath = path.join(self.root, config + SIGNING_PROPERTIES);
if(isAutoGenerated(propertiesFilePath)){ if(isAutoGenerated(propertiesFilePath)){
shell.rm('-f', propertiesFilePath); shell.rm('-f', propertiesFilePath);
} }
}); });
}); });
}; };
module.exports = AntBuilder; module.exports = AntBuilder;
function hasCustomRules(projectRoot) { function hasCustomRules(projectRoot) {
return fs.existsSync(path.join(projectRoot, 'custom_rules.xml')); return fs.existsSync(path.join(projectRoot, 'custom_rules.xml'));
} }
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

@ -1,213 +1,213 @@
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var Q = require('q'); var Q = require('q');
var fs = require('fs'); var fs = require('fs');
var util = require('util'); var util = require('util');
var path = require('path'); var path = require('path');
var shell = require('shelljs'); var shell = require('shelljs');
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;
var check_reqs = require('../check_reqs'); var check_reqs = require('../check_reqs');
var GenericBuilder = require('./GenericBuilder'); var GenericBuilder = require('./GenericBuilder');
var MARKER = 'YOUR CHANGES WILL BE ERASED!'; var MARKER = 'YOUR CHANGES WILL BE ERASED!';
var SIGNING_PROPERTIES = '-signing.properties'; var SIGNING_PROPERTIES = '-signing.properties';
var TEMPLATE = var TEMPLATE =
'# This file is automatically generated.\n' + '# This file is automatically generated.\n' +
'# Do not modify this file -- ' + MARKER + '\n'; '# Do not modify this file -- ' + MARKER + '\n';
function GradleBuilder (projectRoot) { function GradleBuilder (projectRoot) {
GenericBuilder.call(this, projectRoot); GenericBuilder.call(this, projectRoot);
this.binDirs = {gradle: this.binDirs.gradle}; this.binDirs = {gradle: this.binDirs.gradle};
} }
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')];
if (opts.arch) { if (opts.arch) {
args.push('-PcdvBuildArch=' + opts.arch); args.push('-PcdvBuildArch=' + opts.arch);
} }
// 10 seconds -> 6 seconds // 10 seconds -> 6 seconds
args.push('-Dorg.gradle.daemon=true'); args.push('-Dorg.gradle.daemon=true');
args.push.apply(args, opts.extraArgs); args.push.apply(args, opts.extraArgs);
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet): // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
// args.push('-Dorg.gradle.parallel=true'); // args.push('-Dorg.gradle.parallel=true');
return args; return args;
}; };
// 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();
var subProjects = propertiesObj.libs; var subProjects = propertiesObj.libs;
for (var i = 0; i < subProjects.length; ++i) { for (var i = 0; i < subProjects.length; ++i) {
if (subProjects[i] !== 'CordovaLib') { if (subProjects[i] !== 'CordovaLib') {
shell.cp('-f', pluginBuildGradle, path.join(this.root, subProjects[i], 'build.gradle')); shell.cp('-f', pluginBuildGradle, path.join(this.root, subProjects[i], 'build.gradle'));
} }
} }
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;
}); });
// Write the settings.gradle file. // Write the settings.gradle file.
fs.writeFileSync(path.join(this.root, 'settings.gradle'), fs.writeFileSync(path.join(this.root, 'settings.gradle'),
'// GENERATED FILE - DO NOT EDIT\n' + '// GENERATED FILE - DO NOT EDIT\n' +
'include ":"\n' + settingsGradlePaths.join('')); 'include ":"\n' + settingsGradlePaths.join(''));
// Update dependencies within build.gradle. // Update dependencies within build.gradle.
var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8'); var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8');
var depsList = ''; var depsList = '';
subProjects.forEach(function(p) { subProjects.forEach(function(p) {
var libName=p.replace(/[/\\]/g, ':').replace(name+'-',''); var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n'; depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n';
depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n'; depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n';
}); });
// For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390 // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
var SYSTEM_LIBRARY_MAPPINGS = [ var SYSTEM_LIBRARY_MAPPINGS = [
[/^\/?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)) {
mavenRef = p; mavenRef = p;
} else { } else {
for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) { for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
var pair = SYSTEM_LIBRARY_MAPPINGS[i]; var pair = SYSTEM_LIBRARY_MAPPINGS[i];
if (pair[0].exec(p)) { if (pair[0].exec(p)) {
mavenRef = p.replace(pair[0], pair[1]); mavenRef = p.replace(pair[0], pair[1]);
break; break;
} }
} }
if (!mavenRef) { if (!mavenRef) {
throw new CordovaError('Unsupported system library (does not work with gradle): ' + p); throw new CordovaError('Unsupported system library (does not work with gradle): ' + p);
} }
} }
depsList += ' compile "' + mavenRef + '"\n'; depsList += ' compile "' + mavenRef + '"\n';
}); });
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() { .then(function() {
return self.prepBuildFiles(); return self.prepBuildFiles();
}).then(function() { }).then(function() {
// Copy the gradle wrapper on each build so that: // Copy the gradle wrapper on each build so that:
// A) we don't require the Android SDK at project creation time, and // A) we don't require the Android SDK at project creation time, and
// B) we always use the SDK's latest version of it. // B) we always use the SDK's latest version of it.
// check_reqs ensures that this is set. // check_reqs ensures that this is set.
/*jshint -W069 */ /*jshint -W069 */
var sdkDir = process.env['ANDROID_HOME']; var sdkDir = process.env['ANDROID_HOME'];
/*jshint +W069 */ /*jshint +W069 */
var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
if (process.platform == 'win32') { if (process.platform == 'win32') {
shell.rm('-f', path.join(self.root, 'gradlew.bat')); shell.rm('-f', path.join(self.root, 'gradlew.bat'));
shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root); shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root);
} else { } else {
shell.rm('-f', path.join(self.root, 'gradlew')); shell.rm('-f', path.join(self.root, 'gradlew'));
shell.cp(path.join(wrapperDir, 'gradlew'), self.root); shell.cp(path.join(wrapperDir, 'gradlew'), self.root);
} }
shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper')); shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper'));
shell.mkdir('-p', path.join(self.root, 'gradle')); shell.mkdir('-p', path.join(self.root, 'gradle'));
shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle')); shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle'));
// If the gradle distribution URL is set, make sure it points to version we want. // If the gradle distribution URL is set, make sure it points to version we want.
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with. // 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'] || 'http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip'; var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'http\\://services.gradle.org/distributions/gradle-2.2.1-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);
if (opts.packageInfo) { if (opts.packageInfo) {
fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties()); fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
} else if (isAutoGenerated(propertiesFilePath)) { } else if (isAutoGenerated(propertiesFilePath)) {
shell.rm('-f', propertiesFilePath); shell.rm('-f', propertiesFilePath);
} }
}); });
}; };
/* /*
* 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 Q().then(function() { return Q().then(function() {
return spawn(wrapper, args, {stdio: 'inherit'}); return spawn(wrapper, args, {stdio: 'inherit'});
}); });
}; };
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 spawn(wrapper, args, {stdio: 'inherit'}); return 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);
} }
}); });
}); });
}; };
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

@ -1,106 +1,106 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var Q = require('q'), var Q = require('q'),
build = require('./build'); 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');
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;
var events = require('cordova-common').events; 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;
}); });
} }
return list; return list;
}); });
}; };
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.'));
} }
// default device // default device
target = target || device_list[0]; target = target || device_list[0];
if (device_list.indexOf(target) < 0) { if (device_list.indexOf(target) < 0) {
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 };
}); });
}); });
}; };
/* /*
* Installs a previously built application on the device * Installs a previously built application on the device
* 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, '../../AndroidManifest.xml')); var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml'));
var pkgName = manifest.getPackageId(); var pkgName = manifest.getPackageId();
var launchName = pkgName + '/.' + manifest.getActivity().getName(); var launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path); events.emit('log', 'Using apk: ' + apk_path);
// 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

@ -1,372 +1,372 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
/* jshint sub:true */ /* jshint sub:true */
var retry = require('./retry'); var retry = require('./retry');
var build = require('./build'); var build = require('./build');
var check_reqs = require('./check_reqs'); var check_reqs = require('./check_reqs');
var path = require('path'); var path = require('path');
var Adb = require('./Adb'); var Adb = require('./Adb');
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
var events = require('cordova-common').events; 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;
var Q = require('q'); var Q = require('q');
var os = require('os'); var os = require('os');
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 EXEC_KILL_SIGNAL = 'SIGKILL'; var EXEC_KILL_SIGNAL = 'SIGKILL';
/** /**
* Returns a Promise for a list of emulator images in the form of objects * Returns a Promise for a list of emulator images in the form of objects
* { * {
name : <emulator_name>, name : <emulator_name>,
path : <path_to_emulator_image>, path : <path_to_emulator_image>,
target : <api_target>, target : <api_target>,
abi : <cpu>, abi : <cpu>,
skin : <skin> skin : <skin>
} }
*/ */
module.exports.list_images = function() { module.exports.list_images = function() {
return spawn('android', ['list', 'avds']) return spawn('android', ['list', 'avds'])
.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++) {
// To return more detailed information use img_obj // To return more detailed information use img_obj
var img_obj = {}; var img_obj = {};
if (response[i].match(/Name:\s/)) { if (response[i].match(/Name:\s/)) {
img_obj['name'] = response[i].split('Name: ')[1].replace('\r', ''); img_obj['name'] = response[i].split('Name: ')[1].replace('\r', '');
if (response[i + 1].match(/Path:\s/)) { if (response[i + 1].match(/Path:\s/)) {
i++; i++;
img_obj['path'] = response[i].split('Path: ')[1].replace('\r', ''); img_obj['path'] = response[i].split('Path: ')[1].replace('\r', '');
} }
if (response[i + 1].match(/\(API\slevel\s/)) { if (response[i + 1].match(/\(API\slevel\s/)) {
i++; i++;
img_obj['target'] = response[i].replace('\r', ''); img_obj['target'] = response[i].replace('\r', '');
} }
if (response[i + 1].match(/ABI:\s/)) { if (response[i + 1].match(/ABI:\s/)) {
i++; i++;
img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', ''); img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', '');
} }
if (response[i + 1].match(/Skin:\s/)) { if (response[i + 1].match(/Skin:\s/)) {
i++; i++;
img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', ''); img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', '');
} }
emulator_list.push(img_obj); emulator_list.push(img_obj);
} }
/* 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;
}); });
}; };
/** /**
* Will return the closest avd to the projects target * Will return the closest avd to the projects target
* 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;
var closest = 9999; var closest = 9999;
var best = images[0]; var best = images[0];
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;
best = images[i]; best = images[i];
} }
} }
} }
return best; return best;
}); });
}; };
// 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.
module.exports.list_targets = function() { module.exports.list_targets = function() {
return spawn('android', ['list', 'targets'], {cwd: os.tmpdir()}) return 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]);
} }
} }
return targets; return targets;
}); });
}; };
/* /*
* Starts an emulator with the given ID, * Starts an emulator with the given ID,
* and returns the started ID of that emulator. * and returns the started ID of that emulator.
* If no ID is given it will use the first image available, * If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?). * if no image is available it will error out (maybe create one?).
* *
* Returns a promise. * Returns a promise.
*/ */
module.exports.start = function(emulator_ID) { module.exports.start = function(emulator_ID) {
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;
} }
var androidCmd = check_reqs.getAbsoluteAndroidCmd(); var androidCmd = check_reqs.getAbsoluteAndroidCmd();
return Q.reject(new CordovaError('No emulator images (avds) found.\n' + return Q.reject(new CordovaError('No emulator images (avds) found.\n' +
'1. Download desired System Image by running: ' + androidCmd + ' sdk\n' + '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' +
'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) {
var uuid = 'cordova_emulator_' + new Date().getTime(); var uuid = 'cordova_emulator_' + new Date().getTime();
var uuidProp = 'emu.uuid=' + uuid; var uuidProp = 'emu.uuid=' + uuid;
var args = ['-avd', emulatorId, '-prop', uuidProp]; var args = ['-avd', emulatorId, '-prop', uuidProp];
// Don't wait for it to finish, since the emulator will probably keep running for a long time. // Don't wait for it to finish, since the emulator will probably keep running for a long time.
child_process child_process
.spawn('emulator', args, { stdio: 'inherit', detached: true }) .spawn('emulator', args, { stdio: 'inherit', detached: true })
.unref(); .unref();
// wait for emulator to start // wait for emulator to start
events.emit('log', 'Waiting for emulator...'); events.emit('log', 'Waiting for emulator...');
return self.wait_for_emulator(uuid); return self.wait_for_emulator(uuid);
}).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('Booting up emulator (this may take a while)...'); process.stdout.write('Booting up emulator (this may take a while)...');
return self.wait_for_boot(emulatorId) return self.wait_for_boot(emulatorId)
.then(function() { .then(function() {
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;
}); });
}); });
}; };
/* /*
* Waits for an emulator with given uuid to apear on the started-emulator list. * Waits for an emulator with given uuid to apear on the started-emulator list.
* Returns a promise with this emulator's ID. * Returns a promise with this emulator's ID.
*/ */
module.exports.wait_for_emulator = function(uuid) { module.exports.wait_for_emulator = function(uuid) {
var self = this; var self = this;
return self.list_started() return self.list_started()
.then(function(new_started) { .then(function(new_started) {
var emulator_id = null; var emulator_id = null;
var promises = []; var promises = [];
new_started.forEach(function (emulator) { new_started.forEach(function (emulator) {
promises.push( promises.push(
Adb.shell(emulator, 'getprop emu.uuid') Adb.shell(emulator, 'getprop emu.uuid')
.then(function (output) { .then(function (output) {
if (output.indexOf(uuid) >= 0) { if (output.indexOf(uuid) >= 0) {
emulator_id = emulator; emulator_id = emulator;
} }
}) })
); );
}); });
return Q.all(promises).then(function () { return Q.all(promises).then(function () {
return emulator_id || self.wait_for_emulator(uuid); return emulator_id || self.wait_for_emulator(uuid);
}); });
}); });
}; };
/* /*
* Waits for the core android process of the emulator to start * Waits for the core android process of the emulator to start
*/ */
module.exports.wait_for_boot = function(emulator_id) { module.exports.wait_for_boot = function(emulator_id) {
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; return;
} else { } else {
process.stdout.write('.'); process.stdout.write('.');
return Q.delay(3000).then(function() { return Q.delay(3000).then(function() {
return self.wait_for_boot(emulator_id); return self.wait_for_boot(emulator_id);
}); });
} }
}); });
}; };
/* /*
* Create avd * Create avd
* 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 avd named ' + name); console.log('Creating avd named ' + name);
if (target) { if (target) {
return spawn('android', ['create', 'avd', '--name', name, '--target', target]) return 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);
}); });
} 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.');
return spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]) return 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('Please insure you have targets available by running the "android" command'); console.error('Please insure 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 started emulators found, please start an emultor before deploying your project.'); return Q.reject('No started emulators found, please start an emultor before deploying your project.');
} }
// default emulator // default emulator
target = target || emulator_list[0]; target = target || emulator_list[0];
if (emulator_list.indexOf(target) < 0) { if (emulator_list.indexOf(target) < 0) {
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};
}); });
}); });
}; };
/* /*
* Installs a previously built application on the emulator and launches it. * Installs a previously built application on the emulator and launches it.
* If no target is specified, then it picks one. * If no target is specified, then it picks one.
* 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'));
var pkgName = manifest.getPackageId(); var pkgName = manifest.getPackageId();
// 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);
} }
// set the resolved target // set the resolved target
}).then(function (resolvedTarget) { }).then(function (resolvedTarget) {
target = resolvedTarget; target = resolvedTarget;
// install the app // install the app
}).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 Adb.uninstall(target.target, pkgName) return Adb.uninstall(target.target, pkgName)
.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
}; };
events.emit('log', 'Using apk: ' + apk_path); events.emit('log', 'Using apk: ' + apk_path);
events.emit('verbose', 'Installing app on emulator...'); events.emit('verbose', 'Installing app on emulator...');
function exec(command, opts) { function exec(command, opts) {
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));
else resolve(stdout); else resolve(stdout);
}); });
}); });
} }
var retriedInstall = retry.retryPromise( var retriedInstall = retry.retryPromise(
NUM_INSTALL_RETRIES, NUM_INSTALL_RETRIES,
exec, 'adb -s ' + target.target + ' install -r "' + apk_path + '"', execOptions exec, 'adb -s ' + target.target + ' install -r "' + apk_path + '"', execOptions
); );
return retriedInstall.then(function (output) { return retriedInstall.then(function (output) {
if (output.match(/Failure/)) { if (output.match(/Failure/)) {
return Q.reject(new CordovaError('Failed to install apk to emulator: ' + output)); return Q.reject(new CordovaError('Failed to install apk to emulator: ' + output));
} else { } else {
events.emit('log', 'INSTALL SUCCESS'); events.emit('log', 'INSTALL SUCCESS');
} }
}, function (err) { }, function (err) {
return Q.reject(new CordovaError('Failed to install apk to emulator: ' + err)); return Q.reject(new CordovaError('Failed to install apk to emulator: ' + err));
}); });
}); });
// unlock screen // unlock screen
}).then(function () { }).then(function () {
events.emit('verbose', 'Unlocking screen...'); events.emit('verbose', 'Unlocking screen...');
return Adb.shell(target.target, 'input keyevent 82'); return Adb.shell(target.target, 'input keyevent 82');
}).then(function () { }).then(function () {
Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName()); Adb.start(target.target, pkgName + '/.' + manifest.getActivity().getName());
// report success or failure // report success or failure
}).then(function (output) { }).then(function (output) {
events.emit('log', 'LAUNCH SUCCESS'); events.emit('log', 'LAUNCH SUCCESS');
}); });
}; };

View File

@ -1,252 +1,252 @@
/* /*
* *
* Copyright 2013 Anis Kadri * Copyright 2013 Anis Kadri
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, * Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an * software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the * KIND, either express or implied. See the License for the
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
* *
*/ */
/* jshint unused: vars */ /* jshint unused: vars */
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var shell = require('shelljs'); var shell = require('shelljs');
var events = require('cordova-common').events; 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('<source-file> element is missing "src" attribute for plugin: ' + plugin.id); if (!obj.src) throw new CordovaError('<source-file> element is missing "src" attribute for plugin: ' + plugin.id);
if (!obj.targetDir) throw new CordovaError('<source-file> element is missing "target-dir" attribute for plugin: ' + plugin.id); if (!obj.targetDir) throw new CordovaError('<source-file> element is missing "target-dir" attribute for plugin: ' + plugin.id);
var dest = path.join(obj.targetDir, path.basename(obj.src)); var dest = path.join(obj.targetDir, path.basename(obj.src));
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));
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));
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));
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('src not specified in <framework> for plugin: ' + plugin.id); if (!src) throw new CordovaError('src not specified in <framework> for plugin: ' + plugin.id);
events.emit('verbose', 'Installing Android library: ' + src); events.emit('verbose', 'Installing Android library: ' + src);
var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir; var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
var subDir; var subDir;
if (obj.custom) { if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src); var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, options && options.link); copyNewFile(plugin.dir, src, project.projectDir, subRelativeDir, options && options.link);
subDir = path.resolve(project.projectDir, subRelativeDir); subDir = path.resolve(project.projectDir, subRelativeDir);
} else { } else {
obj.type = 'sys'; obj.type = 'sys';
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('src not specified in <framework> for plugin: ' + plugin.id); if (!src) throw new CordovaError('src not specified in <framework> for plugin: ' + plugin.id);
events.emit('verbose', 'Uninstalling Android library: ' + src); events.emit('verbose', 'Uninstalling Android library: ' + src);
var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir; var parentDir = obj.parent ? path.resolve(project.projectDir, obj.parent) : project.projectDir;
var subDir; var subDir;
if (obj.custom) { if (obj.custom) {
var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src); var subRelativeDir = project.getCustomSubprojectRelativeDir(plugin.id, src);
removeFile(project.projectDir, subRelativeDir); removeFile(project.projectDir, subRelativeDir);
subDir = path.resolve(project.projectDir, subRelativeDir); subDir = path.resolve(project.projectDir, subRelativeDir);
// If it's the last framework in the plugin, remove the parent directory. // If it's the last framework in the plugin, remove the parent directory.
var parDir = path.dirname(subDir); var parDir = path.dirname(subDir);
if (fs.readdirSync(parDir).length === 0) { if (fs.readdirSync(parDir).length === 0) {
fs.rmdirSync(parDir); fs.rmdirSync(parDir);
} }
} else { } else {
obj.type = 'sys'; obj.type = 'sys';
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('<asset> tag without required "src" attribute. plugin=' + plugin.dir); throw new CordovaError('<asset> tag without required "src" attribute. plugin=' + plugin.dir);
} }
if (!obj.target) { if (!obj.target) {
throw new CordovaError('<asset> tag without required "target" attribute'); throw new CordovaError('<asset> tag without required "target" attribute');
} }
var www = options.usePlatformWww ? project.platformWww : project.www; var www = options.usePlatformWww ? project.platformWww : project.www;
copyFile(plugin.dir, obj.src, www, obj.target); copyFile(plugin.dir, obj.src, www, 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('<asset> tag without required "target" attribute'); if (!target) throw new CordovaError('<asset> tag without required "target" attribute');
var www = options.usePlatformWww ? project.platformWww : project.www; var www = options.usePlatformWww ? project.platformWww : project.www;
removeFile(www, target); removeFile(www, target);
removeFileF(path.resolve(www, 'plugins', plugin.id)); removeFileF(path.resolve(www, 'plugins', plugin.id));
} }
}, },
'js-module': { 'js-module': {
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.parse(obj.src).name); var moduleName = plugin.id + '.' + (obj.name || path.parse(obj.src).name);
// 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
if (moduleSource.match(/.*\.json$/)) { if (moduleSource.match(/.*\.json$/)) {
scriptContent = 'module.exports = ' + scriptContent; scriptContent = 'module.exports = ' + scriptContent;
} }
scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n'; scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
var www = options.usePlatformWww ? project.platformWww : project.www; var www = options.usePlatformWww ? project.platformWww : project.www;
var moduleDestination = path.resolve(www, 'plugins', plugin.id, obj.src); var moduleDestination = path.resolve(www, 'plugins', plugin.id, obj.src);
shell.mkdir('-p', path.dirname(moduleDestination)); shell.mkdir('-p', path.dirname(moduleDestination));
fs.writeFileSync(moduleDestination, scriptContent, 'utf-8'); fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
}, },
uninstall: function (obj, plugin, project, options) { uninstall: function (obj, plugin, project, options) {
var pluginRelativePath = path.join('plugins', plugin.id, obj.src); var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
var www = options.usePlatformWww ? project.platformWww : project.www; var www = options.usePlatformWww ? project.platformWww : project.www;
removeFileAndParents(www, pluginRelativePath); removeFileAndParents(www, pluginRelativePath);
} }
} }
}; };
module.exports.getInstaller = function (type) { module.exports.getInstaller = function (type) {
if (handlers[type] && handlers[type].install) { if (handlers[type] && handlers[type].install) {
return handlers[type].install; return handlers[type].install;
} }
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;
} }
events.emit('verbose', '<' + type + '> is not supported for android plugins'); events.emit('verbose', '<' + type + '> is not supported for android plugins');
}; };
function copyFile (plugin_dir, src, project_dir, dest, link) { function copyFile (plugin_dir, src, project_dir, dest, link) {
src = path.resolve(plugin_dir, src); src = path.resolve(plugin_dir, src);
if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!'); if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
// 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('"' + src + '" not located within plugin!'); throw new CordovaError('"' + src + '" not located within plugin!');
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('"' + dest + '" not located within project!'); throw new CordovaError('"' + dest + '" not located within project!');
shell.mkdir('-p', path.dirname(dest)); shell.mkdir('-p', path.dirname(dest));
if (link) { if (link) {
fs.symlinkSync(path.relative(path.dirname(dest), src), dest); fs.symlinkSync(path.relative(path.dirname(dest), 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);
} }
} }
// 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);
} }
// checks if file exists and then deletes. Error if doesn't exist // checks if file exists and then deletes. Error if doesn't exist
function removeFile (project_dir, src) { function removeFile (project_dir, src) {
var file = path.resolve(project_dir, src); var file = path.resolve(project_dir, src);
shell.rm('-Rf', file); shell.rm('-Rf', file);
} }
// deletes file/directory without checking // deletes file/directory without checking
function removeFileF (file) { function removeFileF (file) {
shell.rm('-Rf', file); shell.rm('-Rf', file);
} }
// Sometimes we want to remove some java, and prune any unnecessary empty directories // Sometimes we want to remove some java, and prune any unnecessary empty directories
function deleteJava (project_dir, destFile) { function deleteJava (project_dir, destFile) {
removeFileAndParents(project_dir, destFile, 'src'); removeFileAndParents(project_dir, destFile, 'src');
} }
function removeFileAndParents (baseDir, destFile, stopper) { function removeFileAndParents (baseDir, destFile, stopper) {
stopper = stopper || '.'; stopper = stopper || '.';
var file = path.resolve(baseDir, destFile); var file = path.resolve(baseDir, destFile);
if (!fs.existsSync(file)) return; if (!fs.existsSync(file)) return;
removeFileF(file); removeFileF(file);
// 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 {
// directory not empty...do nothing // directory not empty...do nothing
break; break;
} }
} }
} }

View File

@ -1,364 +1,364 @@
/** /**
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var Q = require('q'); var Q = require('q');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var shell = require('shelljs'); var shell = require('shelljs');
var events = require('cordova-common').events; var events = require('cordova-common').events;
var AndroidManifest = require('./AndroidManifest'); var AndroidManifest = require('./AndroidManifest');
var xmlHelpers = require('cordova-common').xmlHelpers; var xmlHelpers = require('cordova-common').xmlHelpers;
var CordovaError = require('cordova-common').CordovaError; var CordovaError = require('cordova-common').CordovaError;
var ConfigParser = require('cordova-common').ConfigParser; var ConfigParser = require('cordova-common').ConfigParser;
module.exports.prepare = function (cordovaProject) { module.exports.prepare = function (cordovaProject) {
var self = this; var self = this;
this._config = updateConfigFilesFrom(cordovaProject.projectConfig, this._config = updateConfigFilesFrom(cordovaProject.projectConfig,
this._munger, this.locations); this._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(updateWwwFrom(cordovaProject, this.locations)) return Q.when(updateWwwFrom(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 () {
handleIcons(cordovaProject.projectConfig, self.root); handleIcons(cordovaProject.projectConfig, self.root);
handleSplashes(cordovaProject.projectConfig, self.root); handleSplashes(cordovaProject.projectConfig, self.root);
}) })
.then(function () { .then(function () {
self.events.emit('verbose', 'updated project successfully'); self.events.emit('verbose', 'updated project successfully');
}); });
}; };
/** /**
* Updates config files in project based on app's config.xml and config munge, * Updates config files in project based on app's config.xml and config munge,
* generated by plugins. * generated by plugins.
* *
* @param {ConfigParser} sourceConfig A project's configuration that will * @param {ConfigParser} sourceConfig A project's configuration that will
* be merged into platform's config.xml * be merged into platform's config.xml
* @param {ConfigChanges} configMunger An initialized ConfigChanges instance * @param {ConfigChanges} configMunger An initialized ConfigChanges instance
* for this platform. * for this platform.
* @param {Object} locations A map of locations for this platform * @param {Object} locations A map of locations for this platform
* *
* @return {ConfigParser} An instance of ConfigParser, that * @return {ConfigParser} An instance of ConfigParser, that
* 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 config.xml from defaults for platform "android"'); events.emit('verbose', 'Generating config.xml from defaults for platform "android"');
// First cleanup current config and merge project's one into own // First cleanup current config and merge project's one into own
// Overwrite platform config.xml with defaults.xml. // Overwrite platform config.xml with defaults.xml.
shell.cp('-f', locations.defaultConfigXml, locations.configXml); shell.cp('-f', locations.defaultConfigXml, locations.configXml);
// Then apply config changes from global munge to all config files // Then apply config changes from global munge to all config files
// in project (including project's config) // in project (including project's config)
configMunger.reapply_global_munge().save_all(); configMunger.reapply_global_munge().save_all();
// 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;
} }
/** /**
* Updates platform 'www' directory by replacing it with contents of * Updates platform 'www' directory by replacing it with contents of
* 'platform_www' and app www. Also copies project's overrides' folder into * 'platform_www' and app www. Also copies project's overrides' folder into
* the platform 'www' folder * the platform 'www' folder
* *
* @param {Object} cordovaProject An object which describes cordova project. * @param {Object} cordovaProject An object which describes cordova project.
* @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 updateWwwFrom(cordovaProject, destinations) { function updateWwwFrom(cordovaProject, destinations) {
shell.rm('-rf', destinations.www); shell.rm('-rf', destinations.www);
shell.mkdir('-p', destinations.www); shell.mkdir('-p', destinations.www);
// Copy source files from project's www directory // Copy source files from project's www directory
shell.cp('-rf', path.join(cordovaProject.locations.www, '*'), destinations.www); shell.cp('-rf', path.join(cordovaProject.locations.www, '*'), destinations.www);
// Override www sources by files in 'platform_www' directory // Override www sources by files in 'platform_www' directory
shell.cp('-rf', path.join(destinations.platformWww, '*'), destinations.www); shell.cp('-rf', path.join(destinations.platformWww, '*'), destinations.www);
// If project contains 'merges' for our platform, use them as another overrides // If project contains 'merges' for our platform, use them as another overrides
var merges_path = path.join(cordovaProject.root, 'merges', 'android'); var merges_path = path.join(cordovaProject.root, 'merges', 'android');
if (fs.existsSync(merges_path)) { if (fs.existsSync(merges_path)) {
events.emit('verbose', 'Found "merges" for android platform. Copying over existing "www" files.'); events.emit('verbose', 'Found "merges" for android platform. Copying over existing "www" files.');
var overrides = path.join(merges_path, '*'); var overrides = path.join(merges_path, '*');
shell.cp('-rf', overrides, destinations.www); shell.cp('-rf', overrides, destinations.www);
} }
} }
/** /**
* Updates project structure and AndroidManifest according to project's configuration. * Updates project structure and AndroidManifest according to project's configuration.
* *
* @param {ConfigParser} platformConfig A project's configuration that will * @param {ConfigParser} platformConfig A project's configuration that will
* 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);
strings.find('string[@name="app_name"]').text = name; strings.find('string[@name="app_name"]').text = name;
fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8'); fs.writeFileSync(locations.strings, strings.write({indent: 4}), 'utf-8');
events.emit('verbose', 'Wrote out Android application name to "' + name + '"'); events.emit('verbose', 'Wrote out Android application name to "' + name + '"');
// Java packages cannot support dashes // Java packages cannot support dashes
var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_'); var pkg = (platformConfig.android_packageName() || platformConfig.packageName()).replace(/-/g, '_');
var manifest = new AndroidManifest(locations.manifest); var manifest = new AndroidManifest(locations.manifest);
var orig_pkg = manifest.getPackageId(); var orig_pkg = manifest.getPackageId();
manifest.getActivity() manifest.getActivity()
.setOrientation(findOrientationValue(platformConfig)) .setOrientation(findOrientationValue(platformConfig))
.setLaunchMode(findAndroidLaunchModePreference(platformConfig)); .setLaunchMode(findAndroidLaunchModePreference(platformConfig));
manifest.setVersionName(platformConfig.version()) manifest.setVersionName(platformConfig.version())
.setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version())) .setVersionCode(platformConfig.android_versionCode() || default_versionCode(platformConfig.version()))
.setPackageId(pkg) .setPackageId(pkg)
.setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android')) .setMinSdkVersion(platformConfig.getPreference('android-minSdkVersion', 'android'))
.setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android')) .setMaxSdkVersion(platformConfig.getPreference('android-maxSdkVersion', 'android'))
.setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android')) .setTargetSdkVersion(platformConfig.getPreference('android-targetSdkVersion', 'android'))
.write(); .write();
var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java'); var javaPattern = path.join(locations.root, 'src', orig_pkg.replace(/\./g, '/'), '*.java');
var java_files = shell.ls(javaPattern).filter(function(f) { var java_files = shell.ls(javaPattern).filter(function(f) {
return shell.grep(/extends\s+CordovaActivity/g, f); return shell.grep(/extends\s+CordovaActivity/g, f);
}); });
if (java_files.length === 0) { if (java_files.length === 0) {
throw new CordovaError('No Java files found which extend CordovaActivity.'); throw new CordovaError('No Java files found which extend CordovaActivity.');
} else if(java_files.length > 1) { } else if(java_files.length > 1) {
events.emit('log', 'Multiple candidate Java files (.java files which extend CordovaActivity) found. Guessing at the first one, ' + java_files[0]); events.emit('log', 'Multiple candidate Java files (.java files which extend CordovaActivity) found. Guessing at the first one, ' + java_files[0]);
} }
var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0])); var destFile = path.join(locations.root, 'src', pkg.replace(/\./g, '/'), path.basename(java_files[0]));
shell.mkdir('-p', path.dirname(destFile)); shell.mkdir('-p', path.dirname(destFile));
shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile); shell.sed(/package [\w\.]*;/, 'package ' + pkg + ';', java_files[0]).to(destFile);
events.emit('verbose', 'Wrote out Android package name to "' + pkg + '"'); events.emit('verbose', 'Wrote out Android package name to "' + pkg + '"');
} }
// 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]) {
versionCode += +nums[0] * 10000; versionCode += +nums[0] * 10000;
} }
if (+nums[1]) { if (+nums[1]) {
versionCode += +nums[1] * 100; versionCode += +nums[1] * 100;
} }
if (+nums[2]) { if (+nums[2]) {
versionCode += +nums[2]; versionCode += +nums[2];
} }
return versionCode; return versionCode;
} }
function copyImage(src, resourcesDir, density, name) { function copyImage(src, resourcesDir, density, name) {
var destFolder = path.join(resourcesDir, (density ? 'drawable-': 'drawable') + density); var destFolder = path.join(resourcesDir, (density ? 'drawable-': 'drawable') + density);
var isNinePatch = !!/\.9\.png$/.exec(src); var isNinePatch = !!/\.9\.png$/.exec(src);
var ninePatchName = name.replace(/\.png$/, '.9.png'); var ninePatchName = name.replace(/\.png$/, '.9.png');
// default template does not have default asset for this density // default template does not have default asset for this density
if (!fs.existsSync(destFolder)) { if (!fs.existsSync(destFolder)) {
fs.mkdirSync(destFolder); fs.mkdirSync(destFolder);
} }
var destFilePath = path.join(destFolder, isNinePatch ? ninePatchName : name); var destFilePath = path.join(destFolder, isNinePatch ? ninePatchName : name);
events.emit('verbose', 'copying image from ' + src + ' to ' + destFilePath); events.emit('verbose', 'copying image from ' + src + ' to ' + destFilePath);
shell.cp('-f', src, destFilePath); shell.cp('-f', src, destFilePath);
} }
function handleSplashes(projectConfig, platformRoot) { function handleSplashes(projectConfig, platformRoot) {
var resources = projectConfig.getSplashScreens('android'); var resources = projectConfig.getSplashScreens('android');
// if there are "splash" elements in config.xml // if there are "splash" elements in config.xml
if (resources.length > 0) { if (resources.length > 0) {
deleteDefaultResourceAt(platformRoot, 'screen.png'); deleteDefaultResourceAt(platformRoot, 'screen.png');
events.emit('verbose', 'splash screens: ' + JSON.stringify(resources)); events.emit('verbose', 'splash screens: ' + JSON.stringify(resources));
// The source paths for icons and splashes are relative to // The source paths for icons and splashes are relative to
// project's config.xml location, so we use it as base path. // project's config.xml location, so we use it as base path.
var projectRoot = path.dirname(projectConfig.path); var projectRoot = path.dirname(projectConfig.path);
var destination = path.join(platformRoot, 'res'); var destination = path.join(platformRoot, 'res');
var hadMdpi = false; var hadMdpi = false;
resources.forEach(function (resource) { resources.forEach(function (resource) {
if (!resource.density) { if (!resource.density) {
return; return;
} }
if (resource.density == 'mdpi') { if (resource.density == 'mdpi') {
hadMdpi = true; hadMdpi = true;
} }
copyImage(path.join(projectRoot, resource.src), destination, resource.density, 'screen.png'); copyImage(path.join(projectRoot, resource.src), destination, resource.density, 'screen.png');
}); });
// There's no "default" drawable, so assume default == mdpi. // There's no "default" drawable, so assume default == mdpi.
if (!hadMdpi && resources.defaultResource) { if (!hadMdpi && resources.defaultResource) {
copyImage(path.join(projectRoot, resources.defaultResource.src), destination, 'mdpi', 'screen.png'); copyImage(path.join(projectRoot, resources.defaultResource.src), destination, 'mdpi', 'screen.png');
} }
} }
} }
function handleIcons(projectConfig, platformRoot) { function handleIcons(projectConfig, platformRoot) {
var icons = projectConfig.getIcons('android'); var icons = projectConfig.getIcons('android');
// if there are icon elements in config.xml // if there are icon elements in config.xml
if (icons.length === 0) { if (icons.length === 0) {
events.emit('verbose', 'This app does not have launcher icons defined'); events.emit('verbose', 'This app does not have launcher icons defined');
return; return;
} }
deleteDefaultResourceAt(platformRoot, 'icon.png'); deleteDefaultResourceAt(platformRoot, 'icon.png');
var android_icons = {}; var android_icons = {};
var default_icon; var default_icon;
// http://developer.android.com/design/style/iconography.html // http://developer.android.com/design/style/iconography.html
var sizeToDensityMap = { var sizeToDensityMap = {
36: 'ldpi', 36: 'ldpi',
48: 'mdpi', 48: 'mdpi',
72: 'hdpi', 72: 'hdpi',
96: 'xhdpi', 96: 'xhdpi',
144: 'xxhdpi', 144: 'xxhdpi',
192: 'xxxhdpi' 192: 'xxxhdpi'
}; };
// 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) {
// invalid icon defition ( or unsupported size) // invalid icon defition ( or unsupported size)
return; return;
} }
var previous = android_icons[density]; var previous = android_icons[density];
if (previous && previous.platform) { if (previous && previous.platform) {
return; return;
} }
android_icons[density] = icon; android_icons[density] = icon;
}; };
// 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) {
size = icon.height; size = icon.height;
} }
if (!size && !icon.density) { if (!size && !icon.density) {
if (default_icon) { if (default_icon) {
events.emit('verbose', 'more than one default icon: ' + JSON.stringify(icon)); events.emit('verbose', 'more than one default icon: ' + JSON.stringify(icon));
} else { } else {
default_icon = icon; default_icon = icon;
} }
} else { } else {
parseIcon(icon, size); parseIcon(icon, size);
} }
} }
// The source paths for icons and splashes are relative to // The source paths for icons and splashes are relative to
// project's config.xml location, so we use it as base path. // project's config.xml location, so we use it as base path.
var projectRoot = path.dirname(projectConfig.path); var projectRoot = path.dirname(projectConfig.path);
var destination = path.join(platformRoot, 'res'); var destination = path.join(platformRoot, 'res');
for (var density in android_icons) { for (var density in android_icons) {
copyImage(path.join(projectRoot, android_icons[density].src), destination, density, 'icon.png'); copyImage(path.join(projectRoot, android_icons[density].src), destination, density, 'icon.png');
} }
// There's no "default" drawable, so assume default == mdpi. // There's no "default" drawable, so assume default == mdpi.
if (default_icon && !android_icons.mdpi) { if (default_icon && !android_icons.mdpi) {
copyImage(path.join(projectRoot, default_icon.src), destination, 'mdpi', 'icon.png'); copyImage(path.join(projectRoot, default_icon.src), destination, 'mdpi', 'icon.png');
} }
} }
// remove the default resource name from all drawable folders // remove the default resource name from all drawable folders
function deleteDefaultResourceAt(baseDir, resourceName) { function deleteDefaultResourceAt(baseDir, resourceName) {
shell.ls(path.join(baseDir, 'res/drawable-*')) shell.ls(path.join(baseDir, 'res/drawable-*'))
.forEach(function (drawableFolder) { .forEach(function (drawableFolder) {
var imagePath = path.join(drawableFolder, resourceName); var imagePath = path.join(drawableFolder, resourceName);
shell.rm('-f', [imagePath, imagePath.replace(/\.png$/, '.9.png')]); shell.rm('-f', [imagePath, imagePath.replace(/\.png$/, '.9.png')]);
events.emit('verbose', 'Deleted ' + imagePath); events.emit('verbose', 'Deleted ' + imagePath);
}); });
} }
/** /**
* Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns * Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns
* preference value and warns if it doesn't seems to be valid * preference value and warns if it doesn't seems to be valid
* *
* @param {ConfigParser} platformConfig A configParser instance for * @param {ConfigParser} platformConfig A configParser instance for
* platform. * platform.
* *
* @return {String} Preference's value from config.xml or * @return {String} Preference's value from config.xml or
* 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
return 'singleTop'; return 'singleTop';
} }
var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance']; var expectedValues = ['standard', 'singleTop', 'singleTask', 'singleInstance'];
var valid = expectedValues.indexOf(launchMode) >= 0; var valid = expectedValues.indexOf(launchMode) >= 0;
if (!valid) { if (!valid) {
// Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future // Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future
events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' + events.emit('warn', 'Unrecognized value for AndroidLaunchMode preference: ' +
launchMode + '. Expected values are: ' + expectedValues.join(', ')); launchMode + '. Expected values are: ' + expectedValues.join(', '));
} }
return launchMode; return launchMode;
} }
/** /**
* Queries ConfigParser object for the orientation <preference> value. Warns if * Queries ConfigParser object for the orientation <preference> value. Warns if
* global preference value is not supported by platform. * global preference value is not supported by platform.
* *
* @param {Object} platformConfig ConfigParser object * @param {Object} platformConfig ConfigParser object
* *
* @return {String} Global/platform-specific orientation in lower-case * @return {String} Global/platform-specific orientation in lower-case
* (or empty string if both are undefined). * (or empty string if both are undefined).
*/ */
function findOrientationValue(platformConfig) { function findOrientationValue(platformConfig) {
var ORIENTATION_DEFAULT = 'default'; var ORIENTATION_DEFAULT = 'default';
var orientation = platformConfig.getPreference('orientation'); var orientation = platformConfig.getPreference('orientation');
if (!orientation) { if (!orientation) {
return ORIENTATION_DEFAULT; return ORIENTATION_DEFAULT;
} }
var GLOBAL_ORIENTATIONS = ['default', 'portrait','landscape']; var GLOBAL_ORIENTATIONS = ['default', 'portrait','landscape'];
function isSupported(orientation) { function isSupported(orientation) {
return GLOBAL_ORIENTATIONS.indexOf(orientation.toLowerCase()) >= 0; return GLOBAL_ORIENTATIONS.indexOf(orientation.toLowerCase()) >= 0;
} }
// Check if the given global orientation is supported // Check if the given global orientation is supported
if (orientation && isSupported(orientation)) { if (orientation && isSupported(orientation)) {
return orientation; return orientation;
} }
events.emit('warn', 'Unsupported global orientation: ' + orientation + events.emit('warn', 'Unsupported global orientation: ' + orientation +
'. Defaulting to value: ' + ORIENTATION_DEFAULT); '. Defaulting to value: ' + ORIENTATION_DEFAULT);
return ORIENTATION_DEFAULT; return ORIENTATION_DEFAULT;
} }

View File

@ -1,35 +1,35 @@
#!/usr/bin/env node #!/usr/bin/env node
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
distributed with this work for additional information distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the KIND, either express or implied. See the License for the
specific language governing permissions and limitations specific language governing permissions and limitations
under the License. under the License.
*/ */
var path = require('path'); var path = require('path');
var Api = require('./templates/cordova/Api'); var Api = require('./templates/cordova/Api');
var args = require('nopt')({ var args = require('nopt')({
'link': Boolean, 'link': Boolean,
'shared': Boolean, 'shared': Boolean,
'help': Boolean 'help': Boolean
}); });
if (args.help || args.argv.remain.length === 0) { if (args.help || args.argv.remain.length === 0) {
console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'update')) + ' <path_to_project> [--link]'); console.log('Usage: ' + path.relative(process.cwd(), path.join(__dirname, 'update')) + ' <path_to_project> [--link]');
console.log(' --link will use the CordovaLib project directly instead of making a copy.'); console.log(' --link will use the CordovaLib project directly instead of making a copy.');
process.exit(1); process.exit(1);
} }
Api.updatePlatform(args.argv.remain[0], {link: (args.link || args.shared)}).done(); Api.updatePlatform(args.argv.remain[0], {link: (args.link || args.shared)}).done();

View File

@ -1,46 +1,46 @@
{ {
"name": "cordova-android", "name": "cordova-android",
"version": "5.0.0-dev", "version": "5.0.0-dev",
"description": "cordova-android release", "description": "cordova-android release",
"bin": { "bin": {
"create": "bin/create" "create": "bin/create"
}, },
"main": "bin/templates/cordova/Api.js", "main": "bin/templates/cordova/Api.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git" "url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git"
}, },
"keywords": [ "keywords": [
"android", "android",
"cordova", "cordova",
"apache" "apache"
], ],
"scripts": { "scripts": {
"test": "npm run jshint && jasmine-node --color spec", "test": "npm run jshint && jasmine-node --color spec",
"test-build": "rm -rf \"test create\" && node ./bin/create \"test create\" com.test.app 応用 && \"./test create/cordova/build\" && rm -rf \"test create\"", "test-build": "rm -rf \"test create\" && node ./bin/create \"test create\" com.test.app 応用 && \"./test create/cordova/build\" && rm -rf \"test create\"",
"jshint": "node node_modules/jshint/bin/jshint bin && node node_modules/jshint/bin/jshint spec" "jshint": "node node_modules/jshint/bin/jshint bin && node node_modules/jshint/bin/jshint spec"
}, },
"author": "Apache Software Foundation", "author": "Apache Software Foundation",
"license": "Apache version 2.0", "license": "Apache version 2.0",
"dependencies": { "dependencies": {
"cordova-common": "^0.1.0", "cordova-common": "^0.1.0",
"elementtree": "^0.1.6", "elementtree": "^0.1.6",
"nopt": "^3.0.1", "nopt": "^3.0.1",
"properties-parser": "^0.3.0", "properties-parser": "^0.3.0",
"q": "^1.4.1", "q": "^1.4.1",
"shelljs": "^0.5.3" "shelljs": "^0.5.3"
}, },
"bundledDependencies": [ "bundledDependencies": [
"cordova-common", "cordova-common",
"elementtree", "elementtree",
"nopt", "nopt",
"properties-parser", "properties-parser",
"q", "q",
"shelljs" "shelljs"
], ],
"devDependencies": { "devDependencies": {
"jasmine-node": "^1.14.5", "jasmine-node": "^1.14.5",
"jshint": "^2.6.0", "jshint": "^2.6.0",
"promise-matchers": "~0" "promise-matchers": "~0"
} }
} }