arnaucube
arnaucube/commonroutesServerPublic Self-hosted repository
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};