summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/android/cordova/lib/build.js
diff options
context:
space:
mode:
authorRene Ae <aehtyb@gmail.com>2015-12-04 20:32:44 -0600
committerRene Ae <aehtyb@gmail.com>2015-12-04 20:32:44 -0600
commit10efb0f7b426426057fed757fe3c851a249358dd (patch)
treeb80e285251d30fbca36220c932ef180c29c55dcf /StoneIsland/platforms/android/cordova/lib/build.js
parent015b58ff6845b5cb79b13fec109a37b4c10c7813 (diff)
android build
Diffstat (limited to 'StoneIsland/platforms/android/cordova/lib/build.js')
-rwxr-xr-xStoneIsland/platforms/android/cordova/lib/build.js717
1 files changed, 717 insertions, 0 deletions
diff --git a/StoneIsland/platforms/android/cordova/lib/build.js b/StoneIsland/platforms/android/cordova/lib/build.js
new file mode 100755
index 00000000..aa9f3d01
--- /dev/null
+++ b/StoneIsland/platforms/android/cordova/lib/build.js
@@ -0,0 +1,717 @@
+#!/usr/bin/env node
+
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+/* jshint sub:true */
+
+var shell = require('shelljs'),
+ spawn = require('./spawn'),
+ Q = require('q'),
+ path = require('path'),
+ fs = require('fs'),
+ os = require('os'),
+ ROOT = path.join(__dirname, '..', '..');
+var check_reqs = require('./check_reqs');
+var exec = require('./exec');
+
+
+var SIGNING_PROPERTIES = '-signing.properties';
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var TEMPLATE =
+ '# This file is automatically generated.\n' +
+ '# Do not modify this file -- ' + MARKER + '\n';
+
+function findApks(directory) {
+ var ret = [];
+ if (fs.existsSync(directory)) {
+ fs.readdirSync(directory).forEach(function(p) {
+ if (path.extname(p) == '.apk') {
+ ret.push(path.join(directory, p));
+ }
+ });
+ }
+ return ret;
+}
+
+function sortFilesByDate(files) {
+ return files.map(function(p) {
+ return { p: p, t: fs.statSync(p).mtime };
+ }).sort(function(a, b) {
+ var timeDiff = b.t - a.t;
+ return timeDiff === 0 ? a.p.length - b.p.length : timeDiff;
+ }).map(function(p) { return p.p; });
+}
+
+function isAutoGenerated(file) {
+ if(fs.existsSync(file)) {
+ var fileContents = fs.readFileSync(file, 'utf8');
+ return fileContents.indexOf(MARKER) > 0;
+ }
+ return false;
+}
+
+function findOutputApksHelper(dir, build_type, arch) {
+ var ret = findApks(dir).filter(function(candidate) {
+ // Need to choose between release and debug .apk.
+ if (build_type === 'debug') {
+ return /-debug/.exec(candidate) && !/-unaligned|-unsigned/.exec(candidate);
+ }
+ if (build_type === 'release') {
+ return /-release/.exec(candidate) && !/-unaligned/.exec(candidate);
+ }
+ return true;
+ });
+ ret = sortFilesByDate(ret);
+ if (ret.length === 0) {
+ return ret;
+ }
+ // Assume arch-specific build if newest apk has -x86 or -arm.
+ var archSpecific = !!/-x86|-arm/.exec(ret[0]);
+ // And show only arch-specific ones (or non-arch-specific)
+ ret = ret.filter(function(p) {
+ /*jshint -W018 */
+ return !!/-x86|-arm/.exec(p) == archSpecific;
+ /*jshint +W018 */
+ });
+ if (archSpecific && ret.length > 1) {
+ ret = ret.filter(function(p) {
+ return p.indexOf('-' + arch) != -1;
+ });
+ }
+
+ return ret;
+}
+
+function hasCustomRules() {
+ return fs.existsSync(path.join(ROOT, 'custom_rules.xml'));
+}
+
+function extractRealProjectNameFromManifest(projectPath) {
+ var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
+ var manifestData = fs.readFileSync(manifestPath, 'utf8');
+ var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i.exec(manifestData);
+ if (!m) {
+ throw new Error('Could not find package name in ' + manifestPath);
+ }
+
+ var packageName=m[1];
+ var lastDotIndex = packageName.lastIndexOf('.');
+ return packageName.substring(lastDotIndex + 1);
+}
+
+function extractProjectNameFromManifest(projectPath) {
+ var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
+ var manifestData = fs.readFileSync(manifestPath, 'utf8');
+ var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
+ if (!m) {
+ throw new Error('Could not find activity name in ' + manifestPath);
+ }
+ return m[1];
+}
+
+function findAllUniq(data, r) {
+ var s = {};
+ var m;
+ while ((m = r.exec(data))) {
+ s[m[1]] = 1;
+ }
+ return Object.keys(s);
+}
+
+function readProjectProperties() {
+ var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
+ return {
+ libs: findAllUniq(data, /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg),
+ gradleIncludes: findAllUniq(data, /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg),
+ systemLibs: findAllUniq(data, /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg)
+ };
+}
+
+var builders = {
+ ant: {
+ getArgs: function(cmd, opts) {
+ var args = [cmd, '-f', path.join(ROOT, 'build.xml')];
+ // custom_rules.xml is required for incremental builds.
+ if (hasCustomRules()) {
+ args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen');
+ }
+ if(opts.packageInfo) {
+ args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES));
+ }
+ return args;
+ },
+
+ prepEnv: function(opts) {
+ return check_reqs.check_ant()
+ .then(function() {
+ // Copy in build.xml on each build so that:
+ // A) we don't require the Android SDK at project creation time, and
+ // B) we always use the SDK's latest version of it.
+ var sdkDir = process.env['ANDROID_HOME'];
+ var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
+ function writeBuildXml(projectPath) {
+ var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
+ fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
+ if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
+ fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
+ }
+ }
+ writeBuildXml(ROOT);
+ var propertiesObj = readProjectProperties();
+ var subProjects = propertiesObj.libs;
+ for (var i = 0; i < subProjects.length; ++i) {
+ writeBuildXml(path.join(ROOT, subProjects[i]));
+ }
+ if (propertiesObj.systemLibs.length > 0) {
+ throw new Error('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 propertiesFilePath = path.join(ROOT, propertiesFile);
+ if (opts.packageInfo) {
+ fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+ } else if(isAutoGenerated(propertiesFilePath)) {
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ },
+
+ /*
+ * Builds the project with ant.
+ * Returns a promise.
+ */
+ build: function(opts) {
+ // Without our custom_rules.xml, we need to clean before building.
+ var ret = Q();
+ if (!hasCustomRules()) {
+ // clean will call check_ant() for us.
+ ret = this.clean(opts);
+ }
+
+ var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+ return check_reqs.check_ant()
+ .then(function() {
+ console.log('Executing: ant ' + args.join(' '));
+ return spawn('ant', args);
+ });
+ },
+
+ clean: function(opts) {
+ var args = this.getArgs('clean', opts);
+ return check_reqs.check_ant()
+ .then(function() {
+ return spawn('ant', args);
+ });
+ },
+
+ findOutputApks: function(build_type) {
+ var binDir = path.join(ROOT, hasCustomRules() ? 'ant-build' : 'bin');
+ return findOutputApksHelper(binDir, build_type, null);
+ }
+ },
+ gradle: {
+ getArgs: function(cmd, opts) {
+ if (cmd == 'release') {
+ cmd = 'cdvBuildRelease';
+ } else if (cmd == 'debug') {
+ cmd = 'cdvBuildDebug';
+ }
+ var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
+ if (opts.arch) {
+ args.push('-PcdvBuildArch=' + opts.arch);
+ }
+
+ // 10 seconds -> 6 seconds
+ args.push('-Dorg.gradle.daemon=true');
+ args.push.apply(args, opts.extraArgs);
+ // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
+ // args.push('-Dorg.gradle.parallel=true');
+ return args;
+ },
+
+ // Makes the project buildable, minus the gradle wrapper.
+ prepBuildFiles: function() {
+ var projectPath = ROOT;
+ // Update the version of build.gradle in each dependent library.
+ var pluginBuildGradle = path.join(projectPath, 'cordova', 'lib', 'plugin-build.gradle');
+ var propertiesObj = readProjectProperties();
+ var subProjects = propertiesObj.libs;
+ for (var i = 0; i < subProjects.length; ++i) {
+ if (subProjects[i] !== 'CordovaLib') {
+ shell.cp('-f', pluginBuildGradle, path.join(ROOT, subProjects[i], 'build.gradle'));
+ }
+ }
+
+ var name = extractRealProjectNameFromManifest(ROOT);
+ //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
+ var settingsGradlePaths = subProjects.map(function(p){
+ var realDir=p.replace(/[/\\]/g, ':');
+ var libName=realDir.replace(name+'-','');
+ var str='include ":'+libName+'"\n';
+ if(realDir.indexOf(name+'-')!==-1)
+ str+='project(":'+libName+'").projectDir = new File("'+p+'")\n';
+ return str;
+ });
+
+ // Write the settings.gradle file.
+ fs.writeFileSync(path.join(projectPath, 'settings.gradle'),
+ '// GENERATED FILE - DO NOT EDIT\n' +
+ 'include ":"\n' + settingsGradlePaths.join(''));
+ // Update dependencies within build.gradle.
+ var buildGradle = fs.readFileSync(path.join(projectPath, 'build.gradle'), 'utf8');
+ var depsList = '';
+ subProjects.forEach(function(p) {
+ var libName=p.replace(/[/\\]/g, ':').replace(name+'-','');
+ depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n';
+ depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n';
+ });
+ // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
+ var SYSTEM_LIBRARY_MAPPINGS = [
+ [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'],
+ [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+']
+ ];
+ propertiesObj.systemLibs.forEach(function(p) {
+ var mavenRef;
+ // It's already in gradle form if it has two ':'s
+ if (/:.*:/.exec(p)) {
+ mavenRef = p;
+ } else {
+ for (var i = 0; i < SYSTEM_LIBRARY_MAPPINGS.length; ++i) {
+ var pair = SYSTEM_LIBRARY_MAPPINGS[i];
+ if (pair[0].exec(p)) {
+ mavenRef = p.replace(pair[0], pair[1]);
+ break;
+ }
+ }
+ if (!mavenRef) {
+ throw new Error('Unsupported system library (does not work with gradle): ' + p);
+ }
+ }
+ depsList += ' compile "' + mavenRef + '"\n';
+ });
+ buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2');
+ var includeList = '';
+ propertiesObj.gradleIncludes.forEach(function(includePath) {
+ includeList += 'apply from: "' + includePath + '"\n';
+ });
+ buildGradle = buildGradle.replace(/(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/, '$1\n' + includeList + '$2');
+ fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
+ },
+
+ prepEnv: function(opts) {
+ var self = this;
+ return check_reqs.check_gradle()
+ .then(function() {
+ return self.prepBuildFiles();
+ }).then(function() {
+ // Copy the gradle wrapper on each build so that:
+ // A) we don't require the Android SDK at project creation time, and
+ // B) we always use the SDK's latest version of it.
+ var projectPath = ROOT;
+ // check_reqs ensures that this is set.
+ var sdkDir = process.env['ANDROID_HOME'];
+ var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
+ if (process.platform == 'win32') {
+ shell.rm('-f', path.join(projectPath, 'gradlew.bat'));
+ shell.cp(path.join(wrapperDir, 'gradlew.bat'), projectPath);
+ } else {
+ shell.rm('-f', path.join(projectPath, 'gradlew'));
+ shell.cp(path.join(wrapperDir, 'gradlew'), projectPath);
+ }
+ shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper'));
+ shell.mkdir('-p', path.join(projectPath, 'gradle'));
+ shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle'));
+
+ // If the gradle distribution URL is set, make sure it points to version we want.
+ // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
+ // For some reason, using ^ and $ don't work. This does the job, though.
+ var distributionUrlRegex = /distributionUrl.*zip/;
+ var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
+ var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
+ shell.chmod('u+w', gradleWrapperPropertiesPath);
+ shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
+
+ var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+ var propertiesFilePath = path.join(ROOT, propertiesFile);
+ if (opts.packageInfo) {
+ fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+ } else if (isAutoGenerated(propertiesFilePath)) {
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ },
+
+ /*
+ * Builds the project with gradle.
+ * Returns a promise.
+ */
+ build: function(opts) {
+ var wrapper = path.join(ROOT, 'gradlew');
+ var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
+ return Q().then(function() {
+ console.log('Running: ' + wrapper + ' ' + args.join(' '));
+ return spawn(wrapper, args);
+ });
+ },
+
+ clean: function(opts) {
+ var builder = this;
+ var wrapper = path.join(ROOT, 'gradlew');
+ var args = builder.getArgs('clean', opts);
+ return Q().then(function() {
+ console.log('Running: ' + wrapper + ' ' + args.join(' '));
+ return spawn(wrapper, args);
+ });
+ },
+
+ findOutputApks: function(build_type, arch) {
+ var binDir = path.join(ROOT, 'build', 'outputs', 'apk');
+ return findOutputApksHelper(binDir, build_type, arch);
+ }
+ },
+
+ none: {
+ prepEnv: function() {
+ return Q();
+ },
+ build: function() {
+ console.log('Skipping build...');
+ return Q(null);
+ },
+ clean: function() {
+ return Q();
+ },
+ findOutputApks: function(build_type, arch) {
+ return sortFilesByDate(builders.ant.findOutputApks(build_type, arch).concat(builders.gradle.findOutputApks(build_type, arch)));
+ }
+ }
+};
+
+module.exports.isBuildFlag = function(flag) {
+ return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag);
+};
+
+function parseOpts(options, resolvedTarget) {
+ // Backwards-compatibility: Allow a single string argument
+ if (typeof options == 'string') options = [options];
+
+ var ret = {
+ buildType: 'debug',
+ buildMethod: process.env['ANDROID_BUILD'] || 'gradle',
+ arch: null,
+ extraArgs: []
+ };
+
+ var multiValueArgs = {
+ 'versionCode': true,
+ 'minSdkVersion': true,
+ 'gradleArg': true,
+ 'keystore' : true,
+ 'alias' : true,
+ 'password' : true,
+ 'storePassword' : true,
+ 'keystoreType' : true,
+ 'buildConfig' : true
+ };
+ var packageArgs = {};
+ var buildConfig;
+ // Iterate through command line options
+ for (var i=0; options && (i < options.length); ++i) {
+ if (/^--/.exec(options[i])) {
+ var keyValue = options[i].substring(2).split('=');
+ var flagName = keyValue.shift();
+ var flagValue = keyValue.join('=');
+ if (multiValueArgs[flagName] && !flagValue) {
+ flagValue = options[i + 1];
+ ++i;
+ }
+ switch(flagName) {
+ case 'debug':
+ case 'release':
+ ret.buildType = flagName;
+ break;
+ case 'ant':
+ case 'gradle':
+ ret.buildMethod = flagName;
+ break;
+ case 'device':
+ case 'emulator':
+ // Don't need to do anything special to when building for device vs emulator.
+ // iOS uses this flag to switch on architecture.
+ break;
+ case 'prepenv' :
+ ret.prepEnv = true;
+ break;
+ case 'nobuild' :
+ ret.buildMethod = 'none';
+ break;
+ case 'versionCode':
+ ret.extraArgs.push('-PcdvVersionCode=' + flagValue);
+ break;
+ case 'minSdkVersion':
+ ret.extraArgs.push('-PcdvMinSdkVersion=' + flagValue);
+ break;
+ case 'gradleArg':
+ ret.extraArgs.push(flagValue);
+ break;
+ case 'keystore':
+ packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue));
+ break;
+ case 'alias':
+ case 'storePassword':
+ case 'password':
+ case 'keystoreType':
+ packageArgs[flagName] = flagValue;
+ break;
+ case 'buildConfig':
+ buildConfig = flagValue;
+ break;
+ default :
+ console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
+ }
+ } else {
+ console.warn('Build option \'' + options[i] + '\' not recognized (ignoring).');
+ }
+ }
+
+ // If some values are not specified as command line arguments - use build config to supplement them.
+ // Command line arguemnts have precedence over build config.
+ if (buildConfig) {
+ if (!fs.existsSync(buildConfig)) {
+ throw new Error('Specified build config file does not exist: ' + buildConfig);
+ }
+ console.log('Reading build config file: '+ path.resolve(buildConfig));
+ var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
+ if (config.android && config.android[ret.buildType]) {
+ var androidInfo = config.android[ret.buildType];
+ if(androidInfo.keystore && !packageArgs.keystore) {
+ if(path.isAbsolute(androidInfo.keystore)) {
+ packageArgs.keystore = androidInfo.keystore;
+ } else {
+ packageArgs.keystore = path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
+ }
+ }
+
+ ['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
+ packageArgs[key] = packageArgs[key] || androidInfo[key];
+ });
+ }
+ }
+ if (packageArgs.keystore && packageArgs.alias) {
+ ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
+ packageArgs.password, packageArgs.keystoreType);
+ }
+
+ if(!ret.packageInfo) {
+ if(Object.keys(packageArgs).length > 0) {
+ console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
+ }
+ }
+ ret.arch = resolvedTarget && resolvedTarget.arch;
+
+ return ret;
+}
+
+/*
+ * Builds the project with the specifed options
+ * Returns a promise.
+ */
+module.exports.runClean = function(options) {
+ var opts = parseOpts(options);
+ var builder = builders[opts.buildMethod];
+ return builder.prepEnv(opts)
+ .then(function() {
+ return builder.clean(opts);
+ }).then(function() {
+ shell.rm('-rf', path.join(ROOT, 'out'));
+
+ ['debug', 'release'].forEach(function(config) {
+ var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES);
+ if(isAutoGenerated(propertiesFilePath)){
+ shell.rm('-f', propertiesFilePath);
+ }
+ });
+ });
+};
+
+/*
+ * Builds the project with the specifed options
+ * Returns a promise.
+ */
+module.exports.run = function(options, optResolvedTarget) {
+ var opts = parseOpts(options, optResolvedTarget);
+ var builder = builders[opts.buildMethod];
+ return builder.prepEnv(opts)
+ .then(function() {
+ if (opts.prepEnv) {
+ console.log('Build file successfully prepared.');
+ return;
+ }
+ return builder.build(opts)
+ .then(function() {
+ var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
+ console.log('Built the following apk(s):');
+ console.log(' ' + apkPaths.join('\n '));
+ return {
+ apkPaths: apkPaths,
+ buildType: opts.buildType,
+ buildMethod: opts.buildMethod
+ };
+ });
+ });
+};
+
+// Called by plugman after installing plugins, and by create script after creating project.
+module.exports.prepBuildFiles = function() {
+ var builder = builders['gradle'];
+ return builder.prepBuildFiles();
+};
+
+/*
+ * Detects the architecture of a device/emulator
+ * Returns "arm" or "x86".
+ */
+module.exports.detectArchitecture = function(target) {
+ function helper() {
+ return exec('adb -s ' + target + ' shell cat /proc/cpuinfo', os.tmpdir())
+ .then(function(output) {
+ if (/intel/i.exec(output)) {
+ return 'x86';
+ }
+ return 'arm';
+ });
+ }
+ // It sometimes happens (at least on OS X), that this command will hang forever.
+ // To fix it, either unplug & replug device, or restart adb server.
+ return helper().timeout(1000, 'Device communication timed out. Try unplugging & replugging the device.')
+ .then(null, function(err) {
+ if (/timed out/.exec('' + err)) {
+ // adb kill-server doesn't seem to do the trick.
+ // Could probably find a x-platform version of killall, but I'm not actually
+ // sure that this scenario even happens on non-OSX machines.
+ return exec('killall adb')
+ .then(function() {
+ console.log('adb seems hung. retrying.');
+ return helper()
+ .then(null, function() {
+ // The double kill is sadly often necessary, at least on mac.
+ console.log('Now device not found... restarting adb again.');
+ return exec('killall adb')
+ .then(function() {
+ return helper()
+ .then(null, function() {
+ return Q.reject('USB is flakey. Try unplugging & replugging the device.');
+ });
+ });
+ });
+ }, function() {
+ // For non-killall OS's.
+ return Q.reject(err);
+ });
+ }
+ throw err;
+ });
+};
+
+module.exports.findBestApkForArchitecture = function(buildResults, arch) {
+ var paths = buildResults.apkPaths.filter(function(p) {
+ if (buildResults.buildType == 'debug') {
+ return /-debug/.exec(p);
+ }
+ return !/-debug/.exec(p);
+ });
+ var archPattern = new RegExp('-' + arch);
+ var hasArchPattern = /-x86|-arm/;
+ for (var i = 0; i < paths.length; ++i) {
+ if (hasArchPattern.exec(paths[i])) {
+ if (archPattern.exec(paths[i])) {
+ return paths[i];
+ }
+ } else {
+ return paths[i];
+ }
+ }
+ throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
+};
+
+function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
+ this.keystore = {
+ 'name': 'key.store',
+ 'value': keystore
+ };
+ this.alias = {
+ 'name': 'key.alias',
+ 'value': alias
+ };
+ if (storePassword) {
+ this.storePassword = {
+ 'name': 'key.store.password',
+ 'value': storePassword
+ };
+ }
+ if (password) {
+ this.password = {
+ 'name': 'key.alias.password',
+ 'value': password
+ };
+ }
+ if (keystoreType) {
+ this.keystoreType = {
+ 'name': 'key.store.type',
+ 'value': keystoreType
+ };
+ }
+}
+
+PackageInfo.prototype = {
+ toProperties: function() {
+ var self = this;
+ var result = '';
+ Object.keys(self).forEach(function(key) {
+ result += self[key].name;
+ result += '=';
+ result += self[key].value.replace(/\\/g, '\\\\');
+ result += '\n';
+ });
+ return result;
+ }
+};
+
+module.exports.help = function() {
+ console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]');
+ console.log('Flags:');
+ console.log(' \'--debug\': will build project in debug mode (default)');
+ console.log(' \'--release\': will build project for release');
+ console.log(' \'--ant\': will build project with ant');
+ console.log(' \'--gradle\': will build project with gradle (default)');
+ console.log(' \'--nobuild\': will skip build process (useful when using run command)');
+ console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary');
+ console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
+ console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
+ console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
+ console.log('');
+ console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
+ console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
+ console.log(' \'--alias=\': Alias for the key store. (Required)');
+ console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)');
+ console.log(' \'--password=\': Password for the key. (Optional - prompted)');
+ console.log(' \'--keystoreType\': Type of the keystore. (Optional)');
+ process.exit(0);
+};