commonroutesServer/www/platforms/android/cordova/lib/build.js
www/platforms/android/cordova/lib/build.jsRaw
1#!/usr/bin/env node
2
3/*
4 Licensed to the Apache Software Foundation (ASF) under one
5 or more contributor license agreements. See the NOTICE file
6 distributed with this work for additional information
7 regarding copyright ownership. The ASF licenses this file
8 to you under the Apache License, Version 2.0 (the
9 "License"); you may not use this file except in compliance
10 with the License. You may obtain a copy of the License at
11
12 http://www.apache.org/licenses/LICENSE-2.0
13
14 Unless required by applicable law or agreed to in writing,
15 software distributed under the License is distributed on an
16 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 KIND, either express or implied. See the License for the
18 specific language governing permissions and limitations
19 under the License.
20*/
21
22var Q = require('q'),
23 path = require('path'),
24 fs = require('fs'),
25 nopt = require('nopt');
26
27var Adb = require('./Adb');
28
29var builders = require('./builders/builders');
30var events = require('cordova-common').events;
31var spawn = require('cordova-common').superspawn.spawn;
32var CordovaError = require('cordova-common').CordovaError;
33
34function parseOpts(options, resolvedTarget, projectRoot) {
35 options = options || {};
36 options.argv = nopt({
37 gradle: Boolean,
38 ant: Boolean,
39 prepenv: Boolean,
40 versionCode: String,
41 minSdkVersion: String,
42 gradleArg: [String, Array],
43 keystore: path,
44 alias: String,
45 storePassword: String,
46 password: String,
47 keystoreType: String
48 }, {}, options.argv, 0);
49
50 var ret = {
51 buildType: options.release ? 'release' : 'debug',
52 buildMethod: process.env.ANDROID_BUILD || 'gradle',
53 prepEnv: options.argv.prepenv,
54 arch: resolvedTarget && resolvedTarget.arch,
55 extraArgs: []
56 };
57
58 if (options.argv.ant || options.argv.gradle)
59 ret.buildMethod = options.argv.ant ? 'ant' : 'gradle';
60
61 if (options.nobuild) ret.buildMethod = 'none';
62
63 if (options.argv.versionCode)
64 ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode);
65
66 if (options.argv.minSdkVersion)
67 ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion);
68
69 if (options.argv.gradleArg) {
70 ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
71 }
72
73 var packageArgs = {};
74
75 if (options.argv.keystore)
76 packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore));
77
78 ['alias','storePassword','password','keystoreType'].forEach(function (flagName) {
79 if (options.argv[flagName])
80 packageArgs[flagName] = options.argv[flagName];
81 });
82
83 var buildConfig = options.buildConfig;
84
85 // If some values are not specified as command line arguments - use build config to supplement them.
86 // Command line arguemnts have precedence over build config.
87 if (buildConfig) {
88 if (!fs.existsSync(buildConfig)) {
89 throw new Error('Specified build config file does not exist: ' + buildConfig);
90 }
91 events.emit('log', 'Reading build config file: '+ path.resolve(buildConfig));
92 var buildjson = fs.readFileSync(buildConfig, 'utf8');
93 var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
94 if (config.android && config.android[ret.buildType]) {
95 var androidInfo = config.android[ret.buildType];
96 if(androidInfo.keystore && !packageArgs.keystore) {
97 if(androidInfo.keystore.substr(0,1) === '~') {
98 androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
99 }
100 packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
101 events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
102 }
103
104 ['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
105 packageArgs[key] = packageArgs[key] || androidInfo[key];
106 });
107 }
108 }
109
110 if (packageArgs.keystore && packageArgs.alias) {
111 ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
112 packageArgs.password, packageArgs.keystoreType);
113 }
114
115 if(!ret.packageInfo) {
116 if(Object.keys(packageArgs).length > 0) {
117 events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
118 }
119 }
120
121 return ret;
122}
123
124/*
125 * Builds the project with the specifed options
126 * Returns a promise.
127 */
128module.exports.runClean = function(options) {
129 var opts = parseOpts(options, null, this.root);
130 var builder = builders.getBuilder(opts.buildMethod);
131 return builder.prepEnv(opts)
132 .then(function() {
133 return builder.clean(opts);
134 });
135};
136
137/**
138 * Builds the project with the specifed options.
139 *
140 * @param {BuildOptions} options A set of options. See PlatformApi.build
141 * method documentation for reference.
142 * @param {Object} optResolvedTarget A deployment target. Used to pass
143 * target architecture from upstream 'run' call. TODO: remove this option in
144 * favor of setting buildOptions.archs field.
145 *
146 * @return {Promise<Object>} Promise, resolved with built packages
147 * information.
148 */
149module.exports.run = function(options, optResolvedTarget) {
150 var opts = parseOpts(options, optResolvedTarget, this.root);
151 var builder = builders.getBuilder(opts.buildMethod);
152 return builder.prepEnv(opts)
153 .then(function() {
154 if (opts.prepEnv) {
155 events.emit('verbose', 'Build file successfully prepared.');
156 return;
157 }
158 return builder.build(opts)
159 .then(function() {
160 var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
161 events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
162 return {
163 apkPaths: apkPaths,
164 buildType: opts.buildType,
165 buildMethod: opts.buildMethod
166 };
167 });
168 });
169};
170
171/*
172 * Detects the architecture of a device/emulator
173 * Returns "arm" or "x86".
174 */
175module.exports.detectArchitecture = function(target) {
176 function helper() {
177 return Adb.shell(target, 'cat /proc/cpuinfo')
178 .then(function(output) {
179 return /intel/i.exec(output) ? 'x86' : 'arm';
180 });
181 }
182 // It sometimes happens (at least on OS X), that this command will hang forever.
183 // To fix it, either unplug & replug device, or restart adb server.
184 return helper()
185 .timeout(1000, new CordovaError('Device communication timed out. Try unplugging & replugging the device.'))
186 .then(null, function(err) {
187 if (/timed out/.exec('' + err)) {
188 // adb kill-server doesn't seem to do the trick.
189 // Could probably find a x-platform version of killall, but I'm not actually
190 // sure that this scenario even happens on non-OSX machines.
191 events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
192 return spawn('killall', ['adb'])
193 .then(function() {
194 return helper()
195 .then(null, function() {
196 // The double kill is sadly often necessary, at least on mac.
197 events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
198 return spawn('killall', ['adb'])
199 .then(function() {
200 return helper()
201 .then(null, function() {
202 return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
203 });
204 });
205 });
206 }, function() {
207 // For non-killall OS's.
208 return Q.reject(err);
209 });
210 }
211 throw err;
212 });
213};
214
215module.exports.findBestApkForArchitecture = function(buildResults, arch) {
216 var paths = buildResults.apkPaths.filter(function(p) {
217 var apkName = path.basename(p);
218 if (buildResults.buildType == 'debug') {
219 return /-debug/.exec(apkName);
220 }
221 return !/-debug/.exec(apkName);
222 });
223 var archPattern = new RegExp('-' + arch);
224 var hasArchPattern = /-x86|-arm/;
225 for (var i = 0; i < paths.length; ++i) {
226 var apkName = path.basename(paths[i]);
227 if (hasArchPattern.exec(apkName)) {
228 if (archPattern.exec(apkName)) {
229 return paths[i];
230 }
231 } else {
232 return paths[i];
233 }
234 }
235 throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
236};
237
238function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
239 this.keystore = {
240 'name': 'key.store',
241 'value': keystore
242 };
243 this.alias = {
244 'name': 'key.alias',
245 'value': alias
246 };
247 if (storePassword) {
248 this.storePassword = {
249 'name': 'key.store.password',
250 'value': storePassword
251 };
252 }
253 if (password) {
254 this.password = {
255 'name': 'key.alias.password',
256 'value': password
257 };
258 }
259 if (keystoreType) {
260 this.keystoreType = {
261 'name': 'key.store.type',
262 'value': keystoreType
263 };
264 }
265}
266
267PackageInfo.prototype = {
268 toProperties: function() {
269 var self = this;
270 var result = '';
271 Object.keys(self).forEach(function(key) {
272 result += self[key].name;
273 result += '=';
274 result += self[key].value.replace(/\\/g, '\\\\');
275 result += '\n';
276 });
277 return result;
278 }
279};
280
281module.exports.help = function() {
282 console.log('Usage: ' + path.relative(process.cwd(), path.join('../build')) + ' [flags] [Signed APK flags]');
283 console.log('Flags:');
284 console.log(' \'--debug\': will build project in debug mode (default)');
285 console.log(' \'--release\': will build project for release');
286 console.log(' \'--ant\': will build project with ant');
287 console.log(' \'--gradle\': will build project with gradle (default)');
288 console.log(' \'--nobuild\': will skip build process (useful when using run command)');
289 console.log(' \'--prepenv\': don\'t build, but copy in build scripts where necessary');
290 console.log(' \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
291 console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
292 console.log(' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
293 console.log('');
294 console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
295 console.log(' \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
296 console.log(' \'--alias=\': Alias for the key store. (Required)');
297 console.log(' \'--storePassword=\': Password for the key store. (Optional - prompted)');
298 console.log(' \'--password=\': Password for the key. (Optional - prompted)');
299 console.log(' \'--keystoreType\': Type of the keystore. (Optional)');
300 process.exit(0);
301};