2015-10-21 07:15:57 +08:00
#!/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 .
* /
2017-06-14 02:42:20 +08:00
var Q = require ( 'q' ) ;
var path = require ( 'path' ) ;
var fs = require ( 'fs' ) ;
var nopt = require ( 'nopt' ) ;
2015-10-21 07:15:57 +08:00
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 ;
2017-06-14 02:42:20 +08:00
function parseOpts ( options , resolvedTarget , projectRoot ) {
2015-10-21 07:15:57 +08:00
options = options || { } ;
options . argv = nopt ( {
gradle : Boolean ,
2017-04-12 04:47:40 +08:00
studio : Boolean ,
2015-10-21 07:15:57 +08:00
prepenv : Boolean ,
versionCode : String ,
minSdkVersion : String ,
2016-03-25 18:31:41 +08:00
gradleArg : [ String , Array ] ,
2015-10-21 07:15:57 +08:00
keystore : path ,
alias : String ,
storePassword : String ,
password : String ,
keystoreType : String
} , { } , options . argv , 0 ) ;
var ret = {
buildType : options . release ? 'release' : 'debug' ,
buildMethod : process . env . ANDROID _BUILD || 'gradle' ,
prepEnv : options . argv . prepenv ,
arch : resolvedTarget && resolvedTarget . arch ,
extraArgs : [ ]
} ;
2017-06-28 04:15:04 +08:00
if ( options . argv . gradle || options . argv . studio ) {
2017-04-12 04:47:40 +08:00
ret . buildMethod = options . argv . studio ? 'studio' : 'gradle' ;
2017-06-28 04:15:04 +08:00
}
2015-10-21 07:15:57 +08:00
2017-06-28 04:15:04 +08:00
// This comes from cordova/run
2017-04-20 02:50:55 +08:00
if ( options . studio ) ret . buildMethod = 'studio' ;
if ( options . gradle ) ret . buildMethod = 'gradle' ;
2015-10-21 07:15:57 +08:00
if ( options . nobuild ) ret . buildMethod = 'none' ;
2017-06-14 02:42:20 +08:00
if ( options . argv . versionCode ) { ret . extraArgs . push ( '-PcdvVersionCode=' + options . argv . versionCode ) ; }
2015-10-21 07:15:57 +08:00
2017-06-14 02:42:20 +08:00
if ( options . argv . minSdkVersion ) { ret . extraArgs . push ( '-PcdvMinSdkVersion=' + options . argv . minSdkVersion ) ; }
2015-10-21 07:15:57 +08:00
2016-03-25 18:31:41 +08:00
if ( options . argv . gradleArg ) {
ret . extraArgs = ret . extraArgs . concat ( options . argv . gradleArg ) ;
}
2015-10-21 07:15:57 +08:00
var packageArgs = { } ;
2017-06-14 02:42:20 +08:00
if ( options . argv . keystore ) { packageArgs . keystore = path . relative ( projectRoot , path . resolve ( options . argv . keystore ) ) ; }
2015-10-21 07:15:57 +08:00
2017-06-14 02:42:20 +08:00
[ 'alias' , 'storePassword' , 'password' , 'keystoreType' ] . forEach ( function ( flagName ) {
if ( options . argv [ flagName ] ) { packageArgs [ flagName ] = options . argv [ flagName ] ; }
2015-10-21 07:15:57 +08:00
} ) ;
var buildConfig = options . buildConfig ;
// 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 ) ;
}
2017-06-14 02:42:20 +08:00
events . emit ( 'log' , 'Reading build config file: ' + path . resolve ( buildConfig ) ) ;
2016-01-12 07:53:00 +08:00
var buildjson = fs . readFileSync ( buildConfig , 'utf8' ) ;
2016-04-06 21:36:43 +08:00
var config = JSON . parse ( buildjson . replace ( /^\ufeff/ , '' ) ) ; // Remove BOM
2015-10-21 07:15:57 +08:00
if ( config . android && config . android [ ret . buildType ] ) {
var androidInfo = config . android [ ret . buildType ] ;
2017-06-14 02:42:20 +08:00
if ( androidInfo . keystore && ! packageArgs . keystore ) {
if ( androidInfo . keystore . substr ( 0 , 1 ) === '~' ) {
2016-01-20 04:01:44 +08:00
androidInfo . keystore = process . env . HOME + androidInfo . keystore . substr ( 1 ) ;
}
2015-10-21 07:15:57 +08:00
packageArgs . keystore = path . resolve ( path . dirname ( buildConfig ) , androidInfo . keystore ) ;
2016-01-12 07:53:00 +08:00
events . emit ( 'log' , 'Reading the keystore from: ' + packageArgs . keystore ) ;
2015-10-21 07:15:57 +08:00
}
2017-06-14 02:42:20 +08:00
[ 'alias' , 'storePassword' , 'password' , 'keystoreType' ] . forEach ( function ( key ) {
2015-10-21 07:15:57 +08:00
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 ) ;
}
2017-06-14 02:42:20 +08:00
if ( ! ret . packageInfo ) {
if ( Object . keys ( packageArgs ) . length > 0 ) {
2015-10-21 07:15:57 +08:00
events . emit ( 'warn' , '\'keystore\' and \'alias\' need to be specified to generate a signed archive.' ) ;
}
}
return ret ;
}
/ *
* Builds the project with the specifed options
* Returns a promise .
* /
2017-06-14 02:42:20 +08:00
module . exports . runClean = function ( options ) {
2016-01-28 15:50:59 +08:00
var opts = parseOpts ( options , null , this . root ) ;
2015-10-21 07:15:57 +08:00
var builder = builders . getBuilder ( opts . buildMethod ) ;
2017-06-14 02:42:20 +08:00
return builder . prepEnv ( opts ) . then ( function ( ) {
2015-10-21 07:15:57 +08:00
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 .
* /
2017-06-14 02:42:20 +08:00
module . exports . run = function ( options , optResolvedTarget ) {
2016-01-28 15:50:59 +08:00
var opts = parseOpts ( options , optResolvedTarget , this . root ) ;
2017-04-20 02:50:55 +08:00
console . log ( opts . buildMethod ) ;
2015-10-21 07:15:57 +08:00
var builder = builders . getBuilder ( opts . buildMethod ) ;
2017-06-14 02:42:20 +08:00
return builder . prepEnv ( opts ) . then ( function ( ) {
2015-10-21 07:15:57 +08:00
if ( opts . prepEnv ) {
2016-03-09 21:56:47 +08:00
events . emit ( 'verbose' , 'Build file successfully prepared.' ) ;
2015-10-21 07:15:57 +08:00
return ;
}
2017-06-14 02:42:20 +08:00
return builder . build ( opts ) . then ( function ( ) {
2015-10-21 07:15:57 +08:00
var apkPaths = builder . findOutputApks ( opts . buildType , opts . arch ) ;
2016-03-09 21:56:47 +08:00
events . emit ( 'log' , 'Built the following apk(s): \n\t' + apkPaths . join ( '\n\t' ) ) ;
2015-10-21 07:15:57 +08:00
return {
apkPaths : apkPaths ,
buildType : opts . buildType ,
buildMethod : opts . buildMethod
} ;
} ) ;
} ) ;
} ;
/ *
* Detects the architecture of a device / emulator
* Returns "arm" or "x86" .
* /
2017-06-14 02:42:20 +08:00
module . exports . detectArchitecture = function ( target ) {
function helper ( ) {
return Adb . shell ( target , 'cat /proc/cpuinfo' ) . then ( function ( output ) {
2015-10-21 07:15:57 +08:00
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.
2017-06-14 02:42:20 +08:00
return helper ( ) . timeout ( 1000 , new CordovaError ( 'Device communication timed out. Try unplugging & replugging the device.' ) ) . then ( null , function ( err ) {
2015-10-21 07:15:57 +08:00
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.
2016-05-04 07:25:48 +08:00
events . emit ( 'verbose' , 'adb timed out while detecting device/emulator architecture. Killing adb and trying again.' ) ;
2017-06-14 02:42:20 +08:00
return spawn ( 'killall' , [ 'adb' ] ) . then ( function ( ) {
return helper ( ) . then ( null , function ( ) {
2015-10-21 07:15:57 +08:00
// The double kill is sadly often necessary, at least on mac.
2016-05-04 07:25:48 +08:00
events . emit ( 'warn' , 'adb timed out a second time while detecting device/emulator architecture. Killing adb and trying again.' ) ;
2017-06-14 02:42:20 +08:00
return spawn ( 'killall' , [ 'adb' ] ) . then ( function ( ) {
return helper ( ) . then ( null , function ( ) {
2016-05-04 07:25:48 +08:00
return Q . reject ( new CordovaError ( 'adb timed out a third time while detecting device/emulator architecture. Try unplugging & replugging the device.' ) ) ;
2015-10-21 07:15:57 +08:00
} ) ;
} ) ;
} ) ;
2017-06-14 02:42:20 +08:00
} , function ( ) {
2015-10-21 07:15:57 +08:00
// For non-killall OS's.
return Q . reject ( err ) ;
} ) ;
}
throw err ;
} ) ;
} ;
2017-06-14 02:42:20 +08:00
module . exports . findBestApkForArchitecture = function ( buildResults , arch ) {
var paths = buildResults . apkPaths . filter ( function ( p ) {
2015-10-21 07:15:57 +08:00
var apkName = path . basename ( p ) ;
2017-06-14 02:42:20 +08:00
if ( buildResults . buildType === 'debug' ) {
2015-10-21 07:15:57 +08:00
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 ) ;
} ;
2017-06-14 02:42:20 +08:00
function PackageInfo ( keystore , alias , storePassword , password , keystoreType ) {
2015-10-21 07:15:57 +08:00
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 = {
2017-06-14 02:42:20 +08:00
toProperties : function ( ) {
2015-10-21 07:15:57 +08:00
var self = this ;
var result = '' ;
2017-06-14 02:42:20 +08:00
Object . keys ( self ) . forEach ( function ( key ) {
2015-10-21 07:15:57 +08:00
result += self [ key ] . name ;
result += '=' ;
result += self [ key ] . value . replace ( /\\/g , '\\\\' ) ;
result += '\n' ;
} ) ;
return result ;
}
} ;
2017-06-14 02:42:20 +08:00
module . exports . help = function ( ) {
2015-10-21 07:15:57 +08:00
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 ( ' \'--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 ) ;
} ;