After attending AnDevCon 2013 in San Francisco a few weeks ago and creating my own application, I decided it was time to become more familiar with gradle and the build process. I set out to accomplish two things:
- Auto-increment the version when a new build is created
- Automatically sign the release version of the code with my keystore information
Auto-Increment
I did some searching around the web for the best way to auto-increment version numbers using the gradle build process. The best article I found was on Stack Overflow, but, not being very familiar with gradle or Ruby, I wasn’t exactly sure where to make the changes.
There are actual two version numbers that need to be incremented:
versionCode
is the integer that is used by the app as a relative comparison to other versions. This value is not seen by the user.versionName
is the String that is used to identify the version to users and appears in the app info screen.
I eventually figured it out (with some help from my fellow BlueFletch developers) and came up with the following:
build.gradle
import java.util.regex.Pattern buildscript { repositories { ... } dependencies { ... } } repositories { ... } android { compileSdkVersion 18 buildToolsVersion "17.0.0" defaultConfig { minSdkVersion 11 targetSdkVersion 18 ... } signingConfigs { release } buildTypes { release { ... } debug { } } } dependencies { ... } task incrementVersionCode << { println(":incrementVersionCode - Incrementing Version Code...") def manifestFile = file("src/main/AndroidManifest.xml") def patternVersionCode = Pattern.compile("versionCode=\"(\\d+)\"") def manifestText = manifestFile.getText() def matcherVersionCode = patternVersionCode.matcher(manifestText) matcherVersionCode.find() def mVersionCode = Integer.parseInt(matcherVersionCode.group(1)) def mNextVersionCode = mVersionCode + 1 def manifestContent = matcherVersionCode.replaceAll("versionCode=\"" + mNextVersionCode + "\"") println(":incrementVersionCode - current versionCode=" + mVersionCode); println(":incrementVersionCode - next versionCode=" + mNextVersionCode); manifestFile.write(manifestContent) } task incrementVersionName << { println(":incrementVersionName - Incrementing Version Name...") def manifestFile = file("src/main/AndroidManifest.xml") def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\"") def manifestText = manifestFile.getText() def matcherVersionNumber = patternVersionNumber.matcher(manifestText) matcherVersionNumber.find() def majorVersion = Integer.parseInt(matcherVersionNumber.group(1)) def minorVersion = Integer.parseInt(matcherVersionNumber.group(2)) def pointVersion = Integer.parseInt(matcherVersionNumber.group(3)) def mVersionName = majorVersion + "." + minorVersion + "." + pointVersion def mNextVersionName = majorVersion + "." + minorVersion + "." + (pointVersion + 1) def manifestContent = matcherVersionNumber.replaceAll("versionName=\"" + mNextVersionName + "\"") println(":incrementVersionName - current versionName=" + mVersionName); println(":incrementVersionName - new versionName=" + mNextVersionName); manifestFile.write(manifestContent) } task release << { println(":release - Build and Version Increment") } task debug << { println(":debug - Build") } incrementVersionName.mustRunAfter build incrementVersionCode.mustRunAfter build debug.dependsOn assembleDebug // Uncomment if you want to increment the versionCode and/or versionName when using the debug build //debug.dependsOn incrementVersionCode //debug.dependsOn incrementVersionName release.dependsOn assembleRelease release.dependsOn incrementVersionCode release.dependsOn incrementVersionName |
The above code does a few things:
- Creates a new task for incrementing the versionCode
- Creates a new task for incrementing the versionName
- Creates a new task for a debug build
- Creates a new task for a release build
- Sets the dependency saying that each of those two new tasks must run after the build process
- Sets the dependencies for the debug task to depend on assembleDebug (the pre-existing task for creating an debug build) and optionally a dependency for each of the incrementVersion* tasks – you can uncomment these to increment the versions for the debug builds as well
- Sets the dependencies for the debug task to depend on assembleRelease (the pre-existing task for creating a release build) and for each of the incrementVersion* tasks
The incrementVersionCode
task takes the existing versionCode in AndroidManifest.xml and increments it for the next release.
For example: If before the build the AndroidManfist.xml has versionCode=7, after the build versionCode=8.
The incrementVersionName
task takes the existing versionName in AndroidManifest.xml and increments the point number for the next release. The AndroidManifest.xml files versionName needs to be in the format [major].[minor].[point].
For example: If before the build the AndroidManfist.xml has versionName=1.3.6, after the build versionName=1.3.7
Signing Release APK
The next thing I wanted to figure out was how to put the .apk signing process into the gradle build process. Once again, Stack Overflow provided some guidance, but I was having trouble getting things working.
The preferred path I wanted to take was to have the keystore and passwords referenced in a properties file. Here’s what I came up with:
build.gradle
android { compileSdkVersion 18 buildToolsVersion "17.0.0" defaultConfig { minSdkVersion 11 targetSdkVersion 18 testPackageName "com.bryankrosenbaum.later.test" testInstrumentationRunner "android.test.InstrumentationTestRunner" } signingConfigs { release } buildTypes { release { signingConfig signingConfigs.release } debug { } } } def Properties props = new Properties() def propFile = new File("signing.properties") if (propFile.canRead()) { props.load(new FileInputStream(propFile)) if (props!= null && props.containsKey("KEYSTORE_FILE") && props.containsKey("KEYSTORE_PASSWORD") && props.containsKey("KEY_ALIAS") && props.containsKey("KEY_PASSWORD")) { def keystoreFile = new File(props["KEYSTORE_FILE"]) if (keystoreFile.canRead()) { android.signingConfigs.release.storeFile = keystoreFile android.signingConfigs.release.storePassword = props["KEYSTORE_PASSWORD"] android.signingConfigs.release.keyAlias = props["KEY_ALIAS"] android.signingConfigs.release.keyPassword = props["KEY_PASSWORD"] } else { println("keystore file not found: " + props["KEYSTORE_FILE"]) android.buildTypes.release.signingConfig = null } } else { println("signing.properties found but some entries missing") android.buildTypes.release.signingConfig = null } } else { println("signing.properties not found") android.buildTypes.release.signingConfig = null } |
signing.properties
KEYSTORE_FILE=path\\to\\mykeystore.keystore KEYSTORE_PASSWORD=****** KEY_ALIAS=****** KEY_PASSWORD=****** |
WARNING ABOUT signing.properties
You’ll probably want to make sure that you don’t check in your signing.properties file into version control with your real passwords inside, otherwise anyone can build a release of your app, submit it to the google play store, etc.
What I did is create a signings.properties file with placeholders like the above code shows. Then on the machine that is doing the builds I run the following for git:
git update-index --assume-unchanged path/to/signing.properties
This will tell git to ignore any changes to that file. After that you can put the real values in for those passwords in the signing.properties file on the build machine.
How to Use
To use these features, run gradle release
, which will increment the versionCode and versionName, and will sign the .apk.
To see this in action, check out my build.gradle file in my “Later” Android project: https://github.com/bryanro/later-android/blob/master/Later/build.gradle
The signing.properties file is here.
Hi Bryan,
I have tried to increase the codeVersion even in debug.
But not thing happen. Could you please give me a suggestion?
Here are my modified code
import java.util.regex.Pattern
buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath ‘com.android.tools.build:gradle:0.12.+’
}
}
apply plugin: ‘com.android.application’
android {
compileSdkVersion 19
buildToolsVersion ‘20.0.0’
defaultConfig {
minSdkVersion 17
targetSdkVersion 19
versionCode 1
versionName “1.0”
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.txt’
}
debug{
}
}
}
dependencies {
//compile fileTree(dir: ‘libs’, include: [‘*.jar’])
compile fileTree(dir: ‘libs’, include: [‘android-support-v4.jar’])
compile fileTree(dir: ‘libs’, include: [‘achartengine-1.1.0.jar’])
}
task incrementVersionCode << {
println(":incrementVersionCode – Incrementing Version Code…")
def manifestFile = file("src/main/AndroidManifest.xml")
def patternVersionCode = Pattern.compile("versionCode=\"(\\d+)\"")
def manifestText = manifestFile.getText()
def matcherVersionCode = patternVersionCode.matcher(manifestText)
matcherVersionCode.find()
def mVersionCode = Integer.parseInt(matcherVersionCode.group(1))
def mNextVersionCode = mVersionCode + 1
def manifestContent = matcherVersionCode.replaceAll("versionCode=\"" + mNextVersionCode + "\"")
println(":incrementVersionCode – current versionCode=" + mVersionCode);
println(":incrementVersionCode – next versionCode=" + mNextVersionCode);
manifestFile.write(manifestContent)
}
task incrementVersionName << {
println(":incrementVersionName – Incrementing Version Name…")
def manifestFile = file("src/main/AndroidManifest.xml")
def patternVersionNumber = Pattern.compile("versionName=\"(\\d+)\\.(\\d+)\\.(\\d+)\"")
def manifestText = manifestFile.getText()
def matcherVersionNumber = patternVersionNumber.matcher(manifestText)
matcherVersionNumber.find()
def majorVersion = Integer.parseInt(matcherVersionNumber.group(1))
def minorVersion = Integer.parseInt(matcherVersionNumber.group(2))
def pointVersion = Integer.parseInt(matcherVersionNumber.group(3))
def mVersionName = majorVersion + "." + minorVersion + "." + pointVersion
def mNextVersionName = majorVersion + "." + minorVersion + "." + (pointVersion + 1)
def manifestContent = matcherVersionNumber.replaceAll("versionName=\"" + mNextVersionName + "\"")
println(":incrementVersionName – current versionName=" + mVersionName);
println(":incrementVersionName – new versionName=" + mNextVersionName);
manifestFile.write(manifestContent)
}
task release << {
println(":release – Build and Version Increment")
}
task debug << {
println(":debug – Build")
}
//incrementVersionName.mustRunAfter build
incrementVersionCode.mustRunAfter build
debug.dependsOn assembleDebug
// Uncomment if you want to increment the versionCode and/or versionName when using the debug build
debug.dependsOn incrementVersionCode
debug.dependsOn incrementVersionName
release.dependsOn assembleRelease
release.dependsOn incrementVersionCode
release.dependsOn incrementVersionName
How are you kicking off the build? Are you running
gradle debug
or something else?I also noticed you commented out
incrementVersionName.mustRunAfter build
– that shouldn’t impact anything, but you may want to try uncommenting it.Finally, were you able to get the version to increment with
gradle release
?