2015-10-21 07:15:57 +08:00
/ *
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 .
* /
2022-04-18 09:39:54 +08:00
const path = require ( 'path' ) ;
const fs = require ( 'fs' ) ;
const nopt = require ( 'nopt' ) ;
2021-07-13 18:02:09 +08:00
const untildify = require ( 'untildify' ) ;
2023-04-23 04:00:51 +08:00
const { parseArgsStringToArgv } = require ( 'string-argv' ) ;
2015-10-21 07:15:57 +08:00
2022-04-18 09:39:54 +08:00
const Adb = require ( './Adb' ) ;
2015-10-21 07:15:57 +08:00
2022-04-18 09:39:54 +08:00
const events = require ( 'cordova-common' ) . events ;
const PackageType = require ( './PackageType' ) ;
2015-10-21 07:15:57 +08:00
2018-09-06 10:06:18 +08:00
module . exports . parseBuildOptions = parseOpts ;
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 ( {
prepenv : Boolean ,
versionCode : String ,
minSdkVersion : String ,
2019-04-06 12:28:25 +08:00
maxSdkVersion : String ,
targetSdkVersion : String ,
2023-04-23 04:00:51 +08:00
// This needs to be an array since nopts will parse its entries as further options for this process
// It will be an array of 1 string: [ "string args" ]
2016-03-25 18:31:41 +08:00
gradleArg : [ String , Array ] ,
2023-04-23 04:00:51 +08:00
2015-10-21 07:15:57 +08:00
keystore : path ,
alias : String ,
storePassword : String ,
password : String ,
2019-08-09 00:53:10 +08:00
keystoreType : String ,
packageType : String
2015-10-21 07:15:57 +08:00
} , { } , options . argv , 0 ) ;
2017-11-23 03:39:40 +08:00
// Android Studio Build method is the default
2022-04-18 09:39:54 +08:00
const ret = {
2015-10-21 07:15:57 +08:00
buildType : options . release ? 'release' : 'debug' ,
prepEnv : options . argv . prepenv ,
arch : resolvedTarget && resolvedTarget . arch ,
extraArgs : [ ]
} ;
2017-06-14 02:42:20 +08:00
if ( options . argv . versionCode ) { ret . extraArgs . push ( '-PcdvVersionCode=' + options . argv . versionCode ) ; }
if ( options . argv . minSdkVersion ) { ret . extraArgs . push ( '-PcdvMinSdkVersion=' + options . argv . minSdkVersion ) ; }
2019-04-06 12:28:25 +08:00
if ( options . argv . maxSdkVersion ) { ret . extraArgs . push ( '-PcdvMaxSdkVersion=' + options . argv . maxSdkVersion ) ; }
if ( options . argv . targetSdkVersion ) { ret . extraArgs . push ( '-PcdvTargetSdkVersion=' + options . argv . targetSdkVersion ) ; }
2016-03-25 18:31:41 +08:00
if ( options . argv . gradleArg ) {
2023-04-23 04:00:51 +08:00
const gradleArgs = parseArgsStringToArgv ( options . argv . gradleArg [ 0 ] ) ;
ret . extraArgs = ret . extraArgs . concat ( gradleArgs ) ;
2016-03-25 18:31:41 +08:00
}
2015-10-21 07:15:57 +08:00
2022-04-18 09:39:54 +08:00
const packageArgs = { } ;
2015-10-21 07:15:57 +08:00
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
2019-08-09 00:53:10 +08:00
[ 'alias' , 'storePassword' , 'password' , 'keystoreType' , 'packageType' ] . forEach ( function ( flagName ) {
2017-06-14 02:42:20 +08:00
if ( options . argv [ flagName ] ) { packageArgs [ flagName ] = options . argv [ flagName ] ; }
2015-10-21 07:15:57 +08:00
} ) ;
2022-04-18 09:39:54 +08:00
const buildConfig = options . buildConfig ;
2015-10-21 07:15:57 +08:00
// If some values are not specified as command line arguments - use build config to supplement them.
2019-08-09 00:53:10 +08:00
// Command line arguments have precedence over build config.
2015-10-21 07:15:57 +08:00
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 ) ) ;
2022-04-18 09:39:54 +08:00
const buildjson = fs . readFileSync ( buildConfig , 'utf8' ) ;
const config = JSON . parse ( buildjson . replace ( /^\ufeff/ , '' ) ) ; // Remove BOM
2015-10-21 07:15:57 +08:00
if ( config . android && config . android [ ret . buildType ] ) {
2022-04-18 09:39:54 +08:00
const androidInfo = config . android [ ret . buildType ] ;
2017-06-14 02:42:20 +08:00
if ( androidInfo . keystore && ! packageArgs . keystore ) {
2021-07-13 18:02:09 +08:00
androidInfo . keystore = untildify ( androidInfo . keystore ) ;
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
}
2019-08-09 00:53:10 +08:00
[ 'alias' , 'storePassword' , 'password' , 'keystoreType' , 'packageType' ] . 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 ) {
2019-08-09 00:53:10 +08:00
// 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' ] ;
2020-01-31 21:02:48 +08:00
for ( const key in packageArgs ) {
2019-08-09 00:53:10 +08:00
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 ) {
2015-10-21 07:15:57 +08:00
events . emit ( 'warn' , '\'keystore\' and \'alias\' need to be specified to generate a signed archive.' ) ;
}
}
2019-08-09 00:53:10 +08:00
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 {
2021-07-13 15:34:38 +08:00
if ( ret . buildType === 'release' ) {
ret . packageType = PackageType . BUNDLE ;
} else {
ret . packageType = PackageType . APK ;
}
2019-08-09 00:53:10 +08:00
}
2015-10-21 07:15:57 +08:00
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 ) {
2022-04-18 09:39:54 +08:00
const opts = parseOpts ( options , null , this . root ) ;
const builder = this . _builder ;
2018-09-06 10:06:18 +08:00
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 ) {
2022-04-18 09:39:54 +08:00
const opts = parseOpts ( options , optResolvedTarget , this . root ) ;
const builder = this . _builder ;
2018-09-06 10:06:18 +08:00
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 ( ) {
2022-04-18 09:39:54 +08:00
let paths ;
2019-08-09 00:53:10 +08:00
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' ) ) ;
}
2015-10-21 07:15:57 +08:00
return {
2023-04-06 11:35:00 +08:00
paths ,
2018-09-06 10:06:18 +08:00
buildType : opts . buildType
2015-10-21 07:15:57 +08:00
} ;
} ) ;
} ) ;
} ;
/ *
* Detects the architecture of a device / emulator
* Returns "arm" or "x86" .
* /
2017-06-14 02:42:20 +08:00
module . exports . detectArchitecture = function ( target ) {
2020-10-23 00:03:28 +08:00
return Adb . shell ( target , 'cat /proc/cpuinfo' ) . then ( function ( output ) {
return /intel/i . exec ( output ) ? 'x86' : 'arm' ;
2015-10-21 07:15:57 +08:00
} ) ;
} ;
2017-06-14 02:42:20 +08:00
module . exports . findBestApkForArchitecture = function ( buildResults , arch ) {
2022-04-18 09:39:54 +08:00
const paths = buildResults . apkPaths . filter ( function ( p ) {
const 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 ) ;
} ) ;
2022-04-18 09:39:54 +08:00
const archPattern = new RegExp ( '-' + arch ) ;
const hasArchPattern = /-x86|-arm/ ;
for ( let i = 0 ; i < paths . length ; ++ i ) {
const apkName = path . basename ( paths [ i ] ) ;
2015-10-21 07:15:57 +08:00
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 ) {
2020-01-29 09:12:55 +08:00
const createNameKeyObject = ( name , value ) => ( { name , value : value . replace ( /\\/g , '\\\\' ) } ) ;
this . data = [
createNameKeyObject ( 'key.store' , keystore ) ,
createNameKeyObject ( 'key.alias' , alias )
] ;
if ( storePassword ) this . data . push ( createNameKeyObject ( 'key.store.password' , storePassword ) ) ;
if ( password ) this . data . push ( createNameKeyObject ( 'key.alias.password' , password ) ) ;
if ( keystoreType ) this . data . push ( createNameKeyObject ( 'key.store.type' , keystoreType ) ) ;
2015-10-21 07:15:57 +08:00
}
PackageInfo . prototype = {
2020-01-29 09:12:55 +08:00
appendToProperties : function ( propertiesParser ) {
for ( const { name , value } of this . data ) propertiesParser . set ( name , value ) ;
propertiesParser . save ( ) ;
2015-10-21 07:15:57 +08:00
}
} ;