123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- #!/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.
- */
-
- var Q = require('q');
- var path = require('path');
- var fs = require('fs');
- var nopt = require('nopt');
-
- var Adb = require('./Adb');
-
- var builders = require('./builders/builders');
- var events = require('cordova-common').events;
- var spawn = require('cordova-common').superspawn.spawn;
- var CordovaError = require('cordova-common').CordovaError;
- var PackageType = require('./PackageType');
-
- module.exports.parseBuildOptions = parseOpts;
- function parseOpts (options, resolvedTarget, projectRoot) {
- options = options || {};
- options.argv = nopt({
- prepenv: Boolean,
- versionCode: String,
- minSdkVersion: String,
- maxSdkVersion: String,
- targetSdkVersion: String,
- gradleArg: [String, Array],
- keystore: path,
- alias: String,
- storePassword: String,
- password: String,
- keystoreType: String,
- packageType: String
- }, {}, options.argv, 0);
-
- // Android Studio Build method is the default
- var ret = {
- buildType: options.release ? 'release' : 'debug',
- prepEnv: options.argv.prepenv,
- arch: resolvedTarget && resolvedTarget.arch,
- extraArgs: []
- };
-
- if (options.argv.versionCode) { ret.extraArgs.push('-PcdvVersionCode=' + options.argv.versionCode); }
- if (options.argv.minSdkVersion) { ret.extraArgs.push('-PcdvMinSdkVersion=' + options.argv.minSdkVersion); }
- if (options.argv.maxSdkVersion) { ret.extraArgs.push('-PcdvMaxSdkVersion=' + options.argv.maxSdkVersion); }
- if (options.argv.targetSdkVersion) { ret.extraArgs.push('-PcdvTargetSdkVersion=' + options.argv.targetSdkVersion); }
- if (options.argv.gradleArg) {
- ret.extraArgs = ret.extraArgs.concat(options.argv.gradleArg);
- }
-
- var packageArgs = {};
-
- if (options.argv.keystore) { packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore)); }
-
- ['alias', 'storePassword', 'password', 'keystoreType', 'packageType'].forEach(function (flagName) {
- if (options.argv[flagName]) { packageArgs[flagName] = options.argv[flagName]; }
- });
-
- var buildConfig = options.buildConfig;
-
- // If some values are not specified as command line arguments - use build config to supplement them.
- // Command line arguments have precedence over build config.
- if (buildConfig) {
- if (!fs.existsSync(buildConfig)) {
- throw new Error('Specified build config file does not exist: ' + buildConfig);
- }
- events.emit('log', 'Reading build config file: ' + path.resolve(buildConfig));
- var buildjson = fs.readFileSync(buildConfig, 'utf8');
- var config = JSON.parse(buildjson.replace(/^\ufeff/, '')); // Remove BOM
- if (config.android && config.android[ret.buildType]) {
- var androidInfo = config.android[ret.buildType];
- if (androidInfo.keystore && !packageArgs.keystore) {
- if (androidInfo.keystore.substr(0, 1) === '~') {
- androidInfo.keystore = process.env.HOME + androidInfo.keystore.substr(1);
- }
- packageArgs.keystore = path.resolve(path.dirname(buildConfig), androidInfo.keystore);
- events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
- }
-
- ['alias', 'storePassword', 'password', 'keystoreType', 'packageType'].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) {
- // The following loop is to decide whether to print a warning about generating a signed archive
- // We only want to produce a warning if they are using a config property that is related to signing, but
- // missing the required properties for signing. We don't want to produce a warning if they are simply
- // using a build property that isn't related to signing, such as --packageType
- let shouldWarn = false;
- const signingKeys = ['keystore', 'alias', 'storePassword', 'password', 'keystoreType'];
-
- for (let key in packageArgs) {
- if (!shouldWarn && signingKeys.indexOf(key) > -1) {
- // If we enter this condition, we have a key used for signing a build,
- // but we are missing some required signing properties
- shouldWarn = true;
- }
- }
-
- if (shouldWarn) {
- events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
- }
- }
-
- if (packageArgs.packageType) {
- const VALID_PACKAGE_TYPES = [PackageType.APK, PackageType.BUNDLE];
- if (VALID_PACKAGE_TYPES.indexOf(packageArgs.packageType) === -1) {
- events.emit('warn', '"' + packageArgs.packageType + '" is an invalid packageType. Valid values are: ' + VALID_PACKAGE_TYPES.join(', ') + '\nDefaulting packageType to ' + PackageType.APK);
- ret.packageType = PackageType.APK;
- } else {
- ret.packageType = packageArgs.packageType;
- }
- } else {
- ret.packageType = PackageType.APK;
- }
-
- return ret;
- }
-
- /*
- * Builds the project with the specifed options
- * Returns a promise.
- */
- module.exports.runClean = function (options) {
- var opts = parseOpts(options, null, this.root);
- var builder = builders.getBuilder();
-
- return builder.prepEnv(opts).then(function () {
- return builder.clean(opts);
- });
- };
-
- /**
- * Builds the project with the specifed options.
- *
- * @param {BuildOptions} options A set of options. See PlatformApi.build
- * method documentation for reference.
- * @param {Object} optResolvedTarget A deployment target. Used to pass
- * target architecture from upstream 'run' call. TODO: remove this option in
- * favor of setting buildOptions.archs field.
- *
- * @return {Promise<Object>} Promise, resolved with built packages
- * information.
- */
- module.exports.run = function (options, optResolvedTarget) {
- var opts = parseOpts(options, optResolvedTarget, this.root);
- var builder = builders.getBuilder();
-
- return builder.prepEnv(opts).then(function () {
- if (opts.prepEnv) {
- events.emit('verbose', 'Build file successfully prepared.');
- return;
- }
- return builder.build(opts).then(function () {
- var paths;
- if (opts.packageType === PackageType.BUNDLE) {
- paths = builder.findOutputBundles(opts.buildType);
- events.emit('log', 'Built the following bundle(s): \n\t' + paths.join('\n\t'));
- } else {
- paths = builder.findOutputApks(opts.buildType, opts.arch);
- events.emit('log', 'Built the following apk(s): \n\t' + paths.join('\n\t'));
- }
-
- return {
- paths: paths,
- buildType: opts.buildType
- };
- });
- });
- };
-
- /*
- * Detects the architecture of a device/emulator
- * Returns "arm" or "x86".
- */
- module.exports.detectArchitecture = function (target) {
- function helper () {
- return Adb.shell(target, 'cat /proc/cpuinfo').then(function (output) {
- return /intel/i.exec(output) ? 'x86' : '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, new CordovaError('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.
- events.emit('verbose', 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.');
- return spawn('killall', ['adb']).then(function () {
- return helper().then(null, function () {
- // The double kill is sadly often necessary, at least on mac.
- events.emit('warn', 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.');
- return spawn('killall', ['adb']).then(function () {
- return helper().then(null, function () {
- return Q.reject(new CordovaError('adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.'));
- });
- });
- });
- }, 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) {
- var apkName = path.basename(p);
- if (buildResults.buildType === 'debug') {
- return /-debug/.exec(apkName);
- }
- return !/-debug/.exec(apkName);
- });
- var archPattern = new RegExp('-' + arch);
- var hasArchPattern = /-x86|-arm/;
- for (var i = 0; i < paths.length; ++i) {
- var apkName = path.basename(paths[i]);
- if (hasArchPattern.exec(apkName)) {
- if (archPattern.exec(apkName)) {
- 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('../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(' \'--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.');
- console.log(' \'--minSdkVersion=#\': Override minSdkVersion for this build.');
- console.log(' \'--maxSdkVersion=#\': Override maxSdkVersion for this build. (Not Recommended)');
- console.log(' \'--targetSdkVersion=#\': Override targetSdkVersion for this build.');
- 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(' \'--packageType=<apk|bundle>\': Builds an APK or a bundle');
- 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);
- };
|