2013-08-13 08:07:23 +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 .
* /
2015-02-04 04:10:50 +08:00
/* jshint sub:true */
2013-08-13 08:07:23 +08:00
var shell = require ( 'shelljs' ) ,
2014-01-16 00:41:03 +08:00
spawn = require ( './spawn' ) ,
Q = require ( 'q' ) ,
2013-08-13 08:07:23 +08:00
path = require ( 'path' ) ,
fs = require ( 'fs' ) ,
2014-10-27 22:14:35 +08:00
os = require ( 'os' ) ,
2013-08-13 08:07:23 +08:00
ROOT = path . join ( _ _dirname , '..' , '..' ) ;
2014-08-16 01:58:53 +08:00
var check _reqs = require ( './check_reqs' ) ;
2014-09-16 02:23:26 +08:00
var exec = require ( './exec' ) ;
2013-08-13 08:07:23 +08:00
2015-03-11 09:13:13 +08:00
var SIGNING _PROPERTIES = '-signing.properties' ;
var MARKER = 'YOUR CHANGES WILL BE ERASED!' ;
var TEMPLATE =
2014-08-19 11:21:26 +08:00
'# This file is automatically generated.\n' +
2015-03-11 09:13:13 +08:00
'# Do not modify this file -- ' + MARKER + '\n' ;
2014-08-18 21:44:00 +08:00
2014-09-23 02:23:30 +08:00
function findApks ( directory ) {
var ret = [ ] ;
2014-08-18 21:44:00 +08:00
if ( fs . existsSync ( directory ) ) {
2014-09-23 02:23:30 +08:00
fs . readdirSync ( directory ) . forEach ( function ( p ) {
if ( path . extname ( p ) == '.apk' ) {
ret . push ( path . join ( directory , p ) ) ;
2014-09-17 02:59:43 +08:00
}
2014-09-23 02:23:30 +08:00
} ) ;
2014-08-18 21:44:00 +08:00
}
2014-09-23 02:23:30 +08:00
return ret ;
}
function sortFilesByDate ( files ) {
return files . map ( function ( p ) {
return { p : p , t : fs . statSync ( p ) . mtime } ;
} ) . sort ( function ( a , b ) {
var timeDiff = b . t - a . t ;
return timeDiff === 0 ? a . p . length - b . p . length : timeDiff ;
} ) . map ( function ( p ) { return p . p ; } ) ;
}
2015-03-11 09:13:13 +08:00
function isAutoGenerated ( file ) {
if ( fs . existsSync ( file ) ) {
var fileContents = fs . readFileSync ( file , 'utf8' ) ;
return fileContents . indexOf ( MARKER ) > 0 ;
}
return false ;
}
2014-09-25 04:07:31 +08:00
function findOutputApksHelper ( dir , build _type , arch ) {
2014-09-23 02:23:30 +08:00
var ret = findApks ( dir ) . filter ( function ( candidate ) {
2015-07-23 23:55:11 +08:00
var apkName = path . basename ( candidate ) ;
2014-09-23 02:23:30 +08:00
// Need to choose between release and debug .apk.
if ( build _type === 'debug' ) {
2015-07-23 23:55:11 +08:00
return /-debug/ . exec ( apkName ) && ! /-unaligned|-unsigned/ . exec ( apkName ) ;
2014-09-23 02:23:30 +08:00
}
if ( build _type === 'release' ) {
2015-07-23 23:55:11 +08:00
return /-release/ . exec ( apkName ) && ! /-unaligned/ . exec ( apkName ) ;
2014-09-23 02:23:30 +08:00
}
return true ;
} ) ;
ret = sortFilesByDate ( ret ) ;
if ( ret . length === 0 ) {
return ret ;
}
2015-07-22 23:39:35 +08:00
// Assume arch-specific build if newest apk has -x86 or -arm.
2015-07-23 23:55:11 +08:00
var archSpecific = ! ! /-x86|-arm/ . exec ( path . basename ( ret [ 0 ] ) ) ;
2015-01-20 10:51:57 +08:00
// And show only arch-specific ones (or non-arch-specific)
2014-09-25 04:07:31 +08:00
ret = ret . filter ( function ( p ) {
2015-02-04 04:10:50 +08:00
/*jshint -W018 */
2015-07-23 23:55:11 +08:00
return ! ! /-x86|-arm/ . exec ( path . basename ( p ) ) == archSpecific ;
2015-02-04 04:10:50 +08:00
/*jshint +W018 */
2014-09-23 02:23:30 +08:00
} ) ;
2015-07-22 23:39:35 +08:00
if ( archSpecific && ret . length > 1 ) {
2014-09-25 04:07:31 +08:00
ret = ret . filter ( function ( p ) {
2015-07-23 23:55:11 +08:00
return path . basename ( p ) . indexOf ( '-' + arch ) != - 1 ;
2014-09-25 04:07:31 +08:00
} ) ;
}
2015-07-22 23:39:35 +08:00
2014-09-25 04:07:31 +08:00
return ret ;
2014-08-18 21:44:00 +08:00
}
2014-01-22 04:09:15 +08:00
function hasCustomRules ( ) {
return fs . existsSync ( path . join ( ROOT , 'custom_rules.xml' ) ) ;
}
2014-08-18 21:44:00 +08:00
2015-06-10 12:57:01 +08:00
function extractRealProjectNameFromManifest ( projectPath ) {
var manifestPath = path . join ( projectPath , 'AndroidManifest.xml' ) ;
var manifestData = fs . readFileSync ( manifestPath , 'utf8' ) ;
var m = /<manifest[\s\S]*?package\s*=\s*"(.*?)"/i . exec ( manifestData ) ;
if ( ! m ) {
throw new Error ( 'Could not find package name in ' + manifestPath ) ;
}
var packageName = m [ 1 ] ;
var lastDotIndex = packageName . lastIndexOf ( '.' ) ;
return packageName . substring ( lastDotIndex + 1 ) ;
}
2014-08-19 11:21:26 +08:00
function extractProjectNameFromManifest ( projectPath ) {
var manifestPath = path . join ( projectPath , 'AndroidManifest.xml' ) ;
var manifestData = fs . readFileSync ( manifestPath , 'utf8' ) ;
var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i . exec ( manifestData ) ;
if ( ! m ) {
throw new Error ( 'Could not find activity name in ' + manifestPath ) ;
}
return m [ 1 ] ;
}
2015-02-03 12:26:53 +08:00
function findAllUniq ( data , r ) {
var s = { } ;
2014-08-19 11:21:26 +08:00
var m ;
2015-02-04 04:10:50 +08:00
while ( ( m = r . exec ( data ) ) ) {
2015-02-03 12:26:53 +08:00
s [ m [ 1 ] ] = 1 ;
2015-01-29 05:27:05 +08:00
}
2015-02-03 12:26:53 +08:00
return Object . keys ( s ) ;
}
function readProjectProperties ( ) {
var data = fs . readFileSync ( path . join ( ROOT , 'project.properties' ) , 'utf8' ) ;
2015-01-29 05:27:05 +08:00
return {
2015-02-03 12:26:53 +08:00
libs : findAllUniq ( data , /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg ) ,
gradleIncludes : findAllUniq ( data , /^\s*cordova\.gradle\.include\.\d+=(.*)(?:\s|$)/mg ) ,
systemLibs : findAllUniq ( data , /^\s*cordova\.system\.library\.\d+=(.*)(?:\s|$)/mg )
2015-01-29 05:27:05 +08:00
} ;
2014-08-19 02:51:10 +08:00
}
2014-08-19 04:19:40 +08:00
var builders = {
2014-08-18 21:44:00 +08:00
ant : {
2015-03-11 09:13:13 +08:00
getArgs : function ( cmd , opts ) {
2014-08-18 21:44:00 +08:00
var args = [ cmd , '-f' , path . join ( ROOT , 'build.xml' ) ] ;
// custom_rules.xml is required for incremental builds.
if ( hasCustomRules ( ) ) {
args . push ( '-Dout.dir=ant-build' , '-Dgen.absolute.dir=ant-gen' ) ;
}
2015-03-11 09:13:13 +08:00
if ( opts . packageInfo ) {
args . push ( '-propertyfile=' + path . join ( ROOT , opts . buildType + SIGNING _PROPERTIES ) ) ;
}
2014-08-18 21:44:00 +08:00
return args ;
} ,
2015-03-11 09:13:13 +08:00
prepEnv : function ( opts ) {
2014-08-19 04:19:40 +08:00
return check _reqs . check _ant ( )
. then ( function ( ) {
2014-08-19 11:21:26 +08:00
// Copy in build.xml on each build so that:
// A) we don't require the Android SDK at project creation time, and
// B) we always use the SDK's latest version of it.
var sdkDir = process . env [ 'ANDROID_HOME' ] ;
var buildTemplate = fs . readFileSync ( path . join ( sdkDir , 'tools' , 'lib' , 'build.template' ) , 'utf8' ) ;
function writeBuildXml ( projectPath ) {
var newData = buildTemplate . replace ( 'PROJECT_NAME' , extractProjectNameFromManifest ( ROOT ) ) ;
fs . writeFileSync ( path . join ( projectPath , 'build.xml' ) , newData ) ;
if ( ! fs . existsSync ( path . join ( projectPath , 'local.properties' ) ) ) {
2015-03-11 09:13:13 +08:00
fs . writeFileSync ( path . join ( projectPath , 'local.properties' ) , TEMPLATE ) ;
2014-08-19 11:21:26 +08:00
}
}
writeBuildXml ( ROOT ) ;
2015-02-03 12:26:53 +08:00
var propertiesObj = readProjectProperties ( ) ;
var subProjects = propertiesObj . libs ;
2014-08-19 11:21:26 +08:00
for ( var i = 0 ; i < subProjects . length ; ++ i ) {
writeBuildXml ( path . join ( ROOT , subProjects [ i ] ) ) ;
}
2015-02-03 12:26:53 +08:00
if ( propertiesObj . systemLibs . length > 0 ) {
throw new Error ( 'Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.' ) ;
}
2015-03-11 09:13:13 +08:00
var propertiesFile = opts . buildType + SIGNING _PROPERTIES ;
var propertiesFilePath = path . join ( ROOT , propertiesFile ) ;
if ( opts . packageInfo ) {
fs . writeFileSync ( propertiesFilePath , TEMPLATE + opts . packageInfo . toProperties ( ) ) ;
} else if ( isAutoGenerated ( propertiesFilePath ) ) {
shell . rm ( '-f' , propertiesFilePath ) ;
}
2014-08-19 04:19:40 +08:00
} ) ;
} ,
2014-08-18 21:44:00 +08:00
/ *
* Builds the project with ant .
* Returns a promise .
* /
2015-03-11 09:13:13 +08:00
build : function ( opts ) {
2014-08-19 04:19:40 +08:00
// Without our custom_rules.xml, we need to clean before building.
var ret = Q ( ) ;
if ( ! hasCustomRules ( ) ) {
// clean will call check_ant() for us.
2015-03-11 09:13:13 +08:00
ret = this . clean ( opts ) ;
2014-08-19 04:19:40 +08:00
}
2015-03-11 09:13:13 +08:00
var args = this . getArgs ( opts . buildType == 'debug' ? 'debug' : 'release' , opts ) ;
2014-08-19 04:19:40 +08:00
return check _reqs . check _ant ( )
. then ( function ( ) {
2015-03-11 09:13:13 +08:00
console . log ( 'Executing: ant ' + args . join ( ' ' ) ) ;
2014-08-18 21:44:00 +08:00
return spawn ( 'ant' , args ) ;
} ) ;
} ,
2015-03-11 09:13:13 +08:00
clean : function ( opts ) {
var args = this . getArgs ( 'clean' , opts ) ;
2014-08-19 04:19:40 +08:00
return check _reqs . check _ant ( )
. then ( function ( ) {
return spawn ( 'ant' , args ) ;
} ) ;
} ,
2014-09-23 02:23:30 +08:00
findOutputApks : function ( build _type ) {
var binDir = path . join ( ROOT , hasCustomRules ( ) ? 'ant-build' : 'bin' ) ;
2014-09-25 04:07:31 +08:00
return findOutputApksHelper ( binDir , build _type , null ) ;
2014-08-18 21:44:00 +08:00
}
} ,
gradle : {
2015-03-11 09:13:13 +08:00
getArgs : function ( cmd , opts ) {
2015-01-20 10:56:46 +08:00
if ( cmd == 'release' ) {
cmd = 'cdvBuildRelease' ;
2014-09-25 04:07:31 +08:00
} else if ( cmd == 'debug' ) {
2015-01-20 10:56:46 +08:00
cmd = 'cdvBuildDebug' ;
2014-09-17 02:13:49 +08:00
}
2014-08-18 21:44:00 +08:00
var args = [ cmd , '-b' , path . join ( ROOT , 'build.gradle' ) ] ;
2015-03-11 09:13:13 +08:00
if ( opts . arch ) {
args . push ( '-PcdvBuildArch=' + opts . arch ) ;
2015-01-20 10:56:46 +08:00
}
2014-08-19 02:16:27 +08:00
// 10 seconds -> 6 seconds
args . push ( '-Dorg.gradle.daemon=true' ) ;
2015-03-11 09:13:13 +08:00
args . push . apply ( args , opts . extraArgs ) ;
2014-08-19 02:16:27 +08:00
// Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
// args.push('-Dorg.gradle.parallel=true');
2014-08-18 21:44:00 +08:00
return args ;
} ,
2015-02-13 05:15:43 +08:00
// Makes the project buildable, minus the gradle wrapper.
prepBuildFiles : function ( ) {
var projectPath = ROOT ;
// Update the version of build.gradle in each dependent library.
var pluginBuildGradle = path . join ( projectPath , 'cordova' , 'lib' , 'plugin-build.gradle' ) ;
var propertiesObj = readProjectProperties ( ) ;
var subProjects = propertiesObj . libs ;
for ( var i = 0 ; i < subProjects . length ; ++ i ) {
if ( subProjects [ i ] !== 'CordovaLib' ) {
shell . cp ( '-f' , pluginBuildGradle , path . join ( ROOT , subProjects [ i ] , 'build.gradle' ) ) ;
}
}
2015-07-20 18:00:17 +08:00
var name = extractRealProjectNameFromManifest ( ROOT ) ;
2015-06-10 12:57:01 +08:00
//Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149
var settingsGradlePaths = subProjects . map ( function ( p ) {
var realDir = p . replace ( /[/\\]/g , ':' ) ;
var libName = realDir . replace ( name + '-' , '' ) ;
var str = 'include ":' + libName + '"\n' ;
if ( realDir . indexOf ( name + '-' ) !== - 1 )
str += 'project(":' + libName + '").projectDir = new File("' + p + '")\n' ;
return str ;
} ) ;
2015-02-13 05:15:43 +08:00
// Write the settings.gradle file.
fs . writeFileSync ( path . join ( projectPath , 'settings.gradle' ) ,
'// GENERATED FILE - DO NOT EDIT\n' +
2015-06-10 12:57:01 +08:00
'include ":"\n' + settingsGradlePaths . join ( '' ) ) ;
2015-02-13 05:15:43 +08:00
// Update dependencies within build.gradle.
var buildGradle = fs . readFileSync ( path . join ( projectPath , 'build.gradle' ) , 'utf8' ) ;
var depsList = '' ;
2015-06-10 12:57:01 +08:00
subProjects . forEach ( function ( p ) {
var libName = p . replace ( /[/\\]/g , ':' ) . replace ( name + '-' , '' ) ;
depsList += ' debugCompile project(path: "' + libName + '", configuration: "debug")\n' ;
depsList += ' releaseCompile project(path: "' + libName + '", configuration: "release")\n' ;
2015-02-13 05:15:43 +08:00
} ) ;
// For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390
var SYSTEM _LIBRARY _MAPPINGS = [
[ /^\/?extras\/android\/support\/(.*)$/ , 'com.android.support:support-$1:+' ] ,
[ /^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/ , 'com.google.android.gms:play-services:+' ]
] ;
propertiesObj . systemLibs . forEach ( function ( p ) {
var mavenRef ;
// It's already in gradle form if it has two ':'s
if ( /:.*:/ . exec ( p ) ) {
mavenRef = p ;
} else {
for ( var i = 0 ; i < SYSTEM _LIBRARY _MAPPINGS . length ; ++ i ) {
var pair = SYSTEM _LIBRARY _MAPPINGS [ i ] ;
if ( pair [ 0 ] . exec ( p ) ) {
mavenRef = p . replace ( pair [ 0 ] , pair [ 1 ] ) ;
break ;
}
}
if ( ! mavenRef ) {
throw new Error ( 'Unsupported system library (does not work with gradle): ' + p ) ;
}
}
depsList += ' compile "' + mavenRef + '"\n' ;
} ) ;
buildGradle = buildGradle . replace ( /(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/ , '$1\n' + depsList + ' $2' ) ;
var includeList = '' ;
propertiesObj . gradleIncludes . forEach ( function ( includePath ) {
includeList += 'apply from: "' + includePath + '"\n' ;
} ) ;
buildGradle = buildGradle . replace ( /(PLUGIN GRADLE EXTENSIONS START)[\s\S]*(\/\/ PLUGIN GRADLE EXTENSIONS END)/ , '$1\n' + includeList + '$2' ) ;
fs . writeFileSync ( path . join ( projectPath , 'build.gradle' ) , buildGradle ) ;
} ,
2015-03-11 09:13:13 +08:00
prepEnv : function ( opts ) {
2015-02-13 05:15:43 +08:00
var self = this ;
2014-08-19 11:21:26 +08:00
return check _reqs . check _gradle ( )
. then ( function ( ) {
2015-02-13 05:15:43 +08:00
return self . prepBuildFiles ( ) ;
} ) . then ( function ( ) {
2014-08-19 11:21:26 +08:00
// Copy the gradle wrapper on each build so that:
// A) we don't require the Android SDK at project creation time, and
// B) we always use the SDK's latest version of it.
var projectPath = ROOT ;
// check_reqs ensures that this is set.
var sdkDir = process . env [ 'ANDROID_HOME' ] ;
var wrapperDir = path . join ( sdkDir , 'tools' , 'templates' , 'gradle' , 'wrapper' ) ;
if ( process . platform == 'win32' ) {
2015-04-24 03:38:48 +08:00
shell . rm ( '-f' , path . join ( projectPath , 'gradlew.bat' ) ) ;
2015-04-24 03:34:25 +08:00
shell . cp ( path . join ( wrapperDir , 'gradlew.bat' ) , projectPath ) ;
2014-08-19 11:21:26 +08:00
} else {
2015-04-24 03:38:48 +08:00
shell . rm ( '-f' , path . join ( projectPath , 'gradlew' ) ) ;
2015-04-24 03:34:25 +08:00
shell . cp ( path . join ( wrapperDir , 'gradlew' ) , projectPath ) ;
2014-08-19 11:21:26 +08:00
}
shell . rm ( '-rf' , path . join ( projectPath , 'gradle' , 'wrapper' ) ) ;
shell . mkdir ( '-p' , path . join ( projectPath , 'gradle' ) ) ;
shell . cp ( '-r' , path . join ( wrapperDir , 'gradle' , 'wrapper' ) , path . join ( projectPath , 'gradle' ) ) ;
2014-09-23 09:35:39 +08:00
2014-12-23 00:37:06 +08:00
// If the gradle distribution URL is set, make sure it points to version we want.
2014-09-23 04:04:24 +08:00
// If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with.
2014-12-10 01:57:29 +08:00
// For some reason, using ^ and $ don't work. This does the job, though.
var distributionUrlRegex = /distributionUrl.*zip/ ;
2015-08-14 15:17:02 +08:00
var distributionUrl = process . env [ 'CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL' ] || 'http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip' ;
2014-09-23 04:04:24 +08:00
var gradleWrapperPropertiesPath = path . join ( projectPath , 'gradle' , 'wrapper' , 'gradle-wrapper.properties' ) ;
2015-04-24 03:18:01 +08:00
shell . chmod ( 'u+w' , gradleWrapperPropertiesPath ) ;
2015-08-14 15:17:02 +08:00
shell . sed ( '-i' , distributionUrlRegex , 'distributionUrl=' + distributionUrl , gradleWrapperPropertiesPath ) ;
2015-03-11 09:13:13 +08:00
var propertiesFile = opts . buildType + SIGNING _PROPERTIES ;
var propertiesFilePath = path . join ( ROOT , propertiesFile ) ;
if ( opts . packageInfo ) {
fs . writeFileSync ( propertiesFilePath , TEMPLATE + opts . packageInfo . toProperties ( ) ) ;
} else if ( isAutoGenerated ( propertiesFilePath ) ) {
shell . rm ( '-f' , propertiesFilePath ) ;
}
2014-08-19 11:21:26 +08:00
} ) ;
2014-08-19 04:19:40 +08:00
} ,
2014-08-18 21:44:00 +08:00
/ *
* Builds the project with gradle .
* Returns a promise .
* /
2015-03-11 09:13:13 +08:00
build : function ( opts ) {
2014-08-18 21:44:00 +08:00
var wrapper = path . join ( ROOT , 'gradlew' ) ;
2015-03-11 09:13:13 +08:00
var args = this . getArgs ( opts . buildType == 'debug' ? 'debug' : 'release' , opts ) ;
2014-08-18 21:44:00 +08:00
return Q ( ) . then ( function ( ) {
2015-02-21 02:41:08 +08:00
console . log ( 'Running: ' + wrapper + ' ' + args . join ( ' ' ) ) ;
2014-08-18 21:44:00 +08:00
return spawn ( wrapper , args ) ;
} ) ;
} ,
2015-03-11 09:13:13 +08:00
clean : function ( opts ) {
2014-08-19 04:19:40 +08:00
var builder = this ;
var wrapper = path . join ( ROOT , 'gradlew' ) ;
2015-03-11 09:13:13 +08:00
var args = builder . getArgs ( 'clean' , opts ) ;
2014-08-19 04:19:40 +08:00
return Q ( ) . then ( function ( ) {
2015-02-21 02:41:08 +08:00
console . log ( 'Running: ' + wrapper + ' ' + args . join ( ' ' ) ) ;
2014-08-19 04:19:40 +08:00
return spawn ( wrapper , args ) ;
} ) ;
} ,
2014-09-25 04:07:31 +08:00
findOutputApks : function ( build _type , arch ) {
2014-09-17 01:00:27 +08:00
var binDir = path . join ( ROOT , 'build' , 'outputs' , 'apk' ) ;
2014-09-25 04:07:31 +08:00
return findOutputApksHelper ( binDir , build _type , arch ) ;
2014-08-18 21:44:00 +08:00
}
2014-08-19 04:19:40 +08:00
} ,
none : {
prepEnv : function ( ) {
return Q ( ) ;
} ,
build : function ( ) {
console . log ( 'Skipping build...' ) ;
2014-09-23 02:23:30 +08:00
return Q ( null ) ;
2014-08-19 04:19:40 +08:00
} ,
clean : function ( ) {
return Q ( ) ;
} ,
2014-09-25 04:07:31 +08:00
findOutputApks : function ( build _type , arch ) {
return sortFilesByDate ( builders . ant . findOutputApks ( build _type , arch ) . concat ( builders . gradle . findOutputApks ( build _type , arch ) ) ) ;
2014-09-23 02:23:30 +08:00
}
2014-01-22 04:09:15 +08:00
}
} ;
2015-03-11 09:13:13 +08:00
module . exports . isBuildFlag = function ( flag ) {
return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/ . exec ( flag ) ;
} ;
2014-09-25 04:07:31 +08:00
function parseOpts ( options , resolvedTarget ) {
2014-08-18 21:44:00 +08:00
// Backwards-compatibility: Allow a single string argument
2015-02-04 04:10:50 +08:00
if ( typeof options == 'string' ) options = [ options ] ;
2014-08-18 21:44:00 +08:00
2014-08-19 04:19:40 +08:00
var ret = {
buildType : 'debug' ,
2014-12-03 23:12:31 +08:00
buildMethod : process . env [ 'ANDROID_BUILD' ] || 'gradle' ,
2015-01-07 04:56:09 +08:00
arch : null ,
extraArgs : [ ]
} ;
var multiValueArgs = {
'versionCode' : true ,
'minSdkVersion' : true ,
2015-03-11 09:13:13 +08:00
'gradleArg' : true ,
'keystore' : true ,
'alias' : true ,
'password' : true ,
'storePassword' : true ,
'keystoreType' : true ,
'buildConfig' : true
2014-08-19 04:19:40 +08:00
} ;
2015-03-11 09:13:13 +08:00
var packageArgs = { } ;
var buildConfig ;
2014-08-18 21:44:00 +08:00
// Iterate through command line options
for ( var i = 0 ; options && ( i < options . length ) ; ++ i ) {
2014-09-23 02:23:30 +08:00
if ( /^--/ . exec ( options [ i ] ) ) {
2014-12-24 05:26:43 +08:00
var keyValue = options [ i ] . substring ( 2 ) . split ( '=' ) ;
2015-01-20 10:54:29 +08:00
var flagName = keyValue . shift ( ) ;
var flagValue = keyValue . join ( '=' ) ;
2015-01-07 04:56:09 +08:00
if ( multiValueArgs [ flagName ] && ! flagValue ) {
2014-12-24 05:26:43 +08:00
flagValue = options [ i + 1 ] ;
++ i ;
}
switch ( flagName ) {
2014-08-18 21:44:00 +08:00
case 'debug' :
case 'release' :
2014-12-24 05:26:43 +08:00
ret . buildType = flagName ;
2014-08-18 21:44:00 +08:00
break ;
case 'ant' :
case 'gradle' :
2014-12-24 05:26:43 +08:00
ret . buildMethod = flagName ;
2014-08-18 21:44:00 +08:00
break ;
2014-12-11 09:30:59 +08:00
case 'device' :
case 'emulator' :
// Don't need to do anything special to when building for device vs emulator.
// iOS uses this flag to switch on architecture.
break ;
2015-02-13 05:15:43 +08:00
case 'prepenv' :
ret . prepEnv = true ;
2015-02-19 02:32:21 +08:00
break ;
2014-08-18 21:44:00 +08:00
case 'nobuild' :
2014-08-19 04:19:40 +08:00
ret . buildMethod = 'none' ;
break ;
2014-12-24 05:26:43 +08:00
case 'versionCode' :
2015-01-07 04:56:09 +08:00
ret . extraArgs . push ( '-PcdvVersionCode=' + flagValue ) ;
2014-12-24 05:26:43 +08:00
break ;
case 'minSdkVersion' :
2015-01-07 04:56:09 +08:00
ret . extraArgs . push ( '-PcdvMinSdkVersion=' + flagValue ) ;
break ;
case 'gradleArg' :
ret . extraArgs . push ( flagValue ) ;
2014-12-24 05:26:43 +08:00
break ;
2015-03-11 09:13:13 +08:00
case 'keystore' :
packageArgs . keystore = path . relative ( ROOT , path . resolve ( flagValue ) ) ;
break ;
case 'alias' :
case 'storePassword' :
case 'password' :
case 'keystoreType' :
packageArgs [ flagName ] = flagValue ;
break ;
case 'buildConfig' :
buildConfig = flagValue ;
break ;
2014-08-18 21:44:00 +08:00
default :
2014-12-24 05:26:43 +08:00
console . warn ( 'Build option --\'' + flagName + '\' not recognized (ignoring).' ) ;
2014-08-18 21:44:00 +08:00
}
} else {
2014-12-11 10:16:18 +08:00
console . warn ( 'Build option \'' + options [ i ] + '\' not recognized (ignoring).' ) ;
2014-08-18 21:44:00 +08:00
}
2013-08-13 08:07:23 +08:00
}
2014-09-25 04:07:31 +08:00
2015-03-11 09:13:13 +08:00
// 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 ) ;
}
2015-06-06 01:21:53 +08:00
console . log ( 'Reading build config file: ' + path . resolve ( buildConfig ) ) ;
2015-03-11 09:13:13 +08:00
var config = JSON . parse ( fs . readFileSync ( buildConfig , 'utf8' ) ) ;
if ( config . android && config . android [ ret . buildType ] ) {
var androidInfo = config . android [ ret . buildType ] ;
2015-07-03 06:13:31 +08:00
if ( androidInfo . keystore && ! packageArgs . keystore ) {
if ( path . isAbsolute ( androidInfo . keystore ) ) {
packageArgs . keystore = androidInfo . keystore ;
} else {
packageArgs . keystore = path . relative ( ROOT , path . join ( path . dirname ( buildConfig ) , androidInfo . keystore ) ) ;
}
2015-03-11 09:13:13 +08:00
}
[ 'alias' , 'storePassword' , 'password' , 'keystoreType' ] . 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 ) {
if ( Object . keys ( packageArgs ) . length > 0 ) {
console . warn ( '\'keystore\' and \'alias\' need to be specified to generate a signed archive.' ) ;
}
}
2015-01-20 10:56:46 +08:00
ret . arch = resolvedTarget && resolvedTarget . arch ;
2014-09-25 04:07:31 +08:00
2014-08-19 04:19:40 +08:00
return ret ;
}
2014-08-18 21:44:00 +08:00
2014-08-19 04:19:40 +08:00
/ *
* Builds the project with the specifed options
* Returns a promise .
* /
module . exports . runClean = function ( options ) {
var opts = parseOpts ( options ) ;
var builder = builders [ opts . buildMethod ] ;
2015-03-11 09:13:13 +08:00
return builder . prepEnv ( opts )
2014-08-19 04:19:40 +08:00
. then ( function ( ) {
2015-03-11 09:13:13 +08:00
return builder . clean ( opts ) ;
2014-08-19 11:21:26 +08:00
} ) . then ( function ( ) {
shell . rm ( '-rf' , path . join ( ROOT , 'out' ) ) ;
2015-03-11 09:13:13 +08:00
[ 'debug' , 'release' ] . forEach ( function ( config ) {
var propertiesFilePath = path . join ( ROOT , config + SIGNING _PROPERTIES ) ;
if ( isAutoGenerated ( propertiesFilePath ) ) {
shell . rm ( '-f' , propertiesFilePath ) ;
}
} ) ;
2014-08-19 04:19:40 +08:00
} ) ;
} ;
/ *
* Builds the project with the specifed options
* Returns a promise .
* /
2014-09-25 04:07:31 +08:00
module . exports . run = function ( options , optResolvedTarget ) {
var opts = parseOpts ( options , optResolvedTarget ) ;
2014-08-19 04:19:40 +08:00
var builder = builders [ opts . buildMethod ] ;
2015-03-11 09:13:13 +08:00
return builder . prepEnv ( opts )
2014-08-19 04:19:40 +08:00
. then ( function ( ) {
2015-02-13 05:15:43 +08:00
if ( opts . prepEnv ) {
console . log ( 'Build file successfully prepared.' ) ;
return ;
}
2015-03-11 09:13:13 +08:00
return builder . build ( opts )
2015-02-13 05:15:43 +08:00
. then ( function ( ) {
var apkPaths = builder . findOutputApks ( opts . buildType , opts . arch ) ;
console . log ( 'Built the following apk(s):' ) ;
console . log ( ' ' + apkPaths . join ( '\n ' ) ) ;
return {
apkPaths : apkPaths ,
buildType : opts . buildType ,
buildMethod : opts . buildMethod
} ;
} ) ;
2014-01-22 04:09:15 +08:00
} ) ;
2014-08-18 21:44:00 +08:00
} ;
2013-08-13 08:07:23 +08:00
2015-02-13 05:15:43 +08:00
// Called by plugman after installing plugins, and by create script after creating project.
module . exports . prepBuildFiles = function ( ) {
var builder = builders [ 'gradle' ] ;
return builder . prepBuildFiles ( ) ;
} ;
2014-09-16 02:23:26 +08:00
/ *
* Detects the architecture of a device / emulator
* Returns "arm" or "x86" .
* /
module . exports . detectArchitecture = function ( target ) {
2014-12-04 22:58:00 +08:00
function helper ( ) {
2014-10-27 22:14:35 +08:00
return exec ( 'adb -s ' + target + ' shell cat /proc/cpuinfo' , os . tmpdir ( ) )
2014-12-04 22:58:00 +08:00
. then ( function ( output ) {
if ( /intel/i . exec ( output ) ) {
return 'x86' ;
}
return '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 , '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.
return exec ( 'killall adb' )
. then ( function ( ) {
console . log ( 'adb seems hung. retrying.' ) ;
return helper ( )
. then ( null , function ( ) {
// The double kill is sadly often necessary, at least on mac.
console . log ( 'Now device not found... restarting adb again.' ) ;
return exec ( 'killall adb' )
. then ( function ( ) {
return helper ( )
. then ( null , function ( ) {
return Q . reject ( 'USB is flakey. Try unplugging & replugging the device.' ) ;
} ) ;
} ) ;
} ) ;
} , function ( ) {
// For non-killall OS's.
return Q . reject ( err ) ;
2015-02-04 04:10:50 +08:00
} ) ;
2014-09-16 02:23:26 +08:00
}
2014-12-04 22:58:00 +08:00
throw err ;
2014-09-16 02:23:26 +08:00
} ) ;
} ;
2014-09-23 02:23:30 +08:00
module . exports . findBestApkForArchitecture = function ( buildResults , arch ) {
var paths = buildResults . apkPaths . filter ( function ( p ) {
2015-07-23 23:55:11 +08:00
var apkName = path . basename ( p ) ;
2014-09-23 02:23:30 +08:00
if ( buildResults . buildType == 'debug' ) {
2015-07-23 23:55:11 +08:00
return /-debug/ . exec ( apkName ) ;
2014-09-23 02:23:30 +08:00
}
2015-07-23 23:55:11 +08:00
return ! /-debug/ . exec ( apkName ) ;
2014-09-23 02:23:30 +08:00
} ) ;
var archPattern = new RegExp ( '-' + arch ) ;
var hasArchPattern = /-x86|-arm/ ;
for ( var i = 0 ; i < paths . length ; ++ i ) {
2015-07-23 23:55:11 +08:00
var apkName = path . basename ( paths [ i ] ) ;
if ( hasArchPattern . exec ( apkName ) ) {
if ( archPattern . exec ( apkName ) ) {
2014-09-23 02:23:30 +08:00
return paths [ i ] ;
}
} else {
return paths [ i ] ;
}
2013-08-13 08:07:23 +08:00
}
2014-09-23 02:23:30 +08:00
throw new Error ( 'Could not find apk architecture: ' + arch + ' build-type: ' + buildResults . buildType ) ;
2014-08-18 21:44:00 +08:00
} ;
2013-08-13 08:07:23 +08:00
2015-03-11 09:13:13 +08:00
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 ;
}
} ;
2013-08-13 08:07:23 +08:00
module . exports . help = function ( ) {
2015-03-11 09:13:13 +08:00
console . log ( 'Usage: ' + path . relative ( process . cwd ( ) , path . join ( ROOT , 'cordova' , 'build' ) ) + ' [flags] [Signed APK flags]' ) ;
2014-12-24 05:26:43 +08:00
console . log ( 'Flags:' ) ;
console . log ( ' \'--debug\': will build project in debug mode (default)' ) ;
2014-08-18 21:44:00 +08:00
console . log ( ' \'--release\': will build project for release' ) ;
2014-12-25 02:35:17 +08:00
console . log ( ' \'--ant\': will build project with ant' ) ;
console . log ( ' \'--gradle\': will build project with gradle (default)' ) ;
2014-12-24 05:26:43 +08:00
console . log ( ' \'--nobuild\': will skip build process (useful when using run command)' ) ;
2015-02-13 05:15:43 +08:00
console . log ( ' \'--prepenv\': don\'t build, but copy in build scripts where necessary' ) ;
2014-12-24 05:26:43 +08:00
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.' ) ;
2015-01-07 04:56:09 +08:00
console . log ( ' \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true' ) ;
2015-03-11 09:13:13 +08:00
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)' ) ;
2013-08-13 08:07:23 +08:00
process . exit ( 0 ) ;
2014-08-18 21:44:00 +08:00
} ;