2010-03-16

How to: subversion build number in your Android app

So you're building an Android app, and you want your app to display its build number on an About screen. You want the build number to be a unique identifier like the Subversion revision number. That's easy, you say: I'll build a simple About screen, put a TextView in it, and use Subversion keyword substitution, like this:
package com.kasperowski.example;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class BuildNumberExample extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        View aboutView = aboutWithKeywordSubstitution();
        setContentView(aboutView);
    }

    View aboutWithKeywordSubstitution() {
        TextView aboutView = new TextView(this);
        aboutView.setText("Build number: $Rev$");
        return aboutView;
    }
}
And for bonus points, you could play some regex tricks or something to make the version number look like 42 instead of $Rev: 42$.

But, uh-oh! That's the revision number for that file, not the global revision number for your whole project. So it's no good as a build number.

You could play other games, like using a serial number file and writing a script that increments the serial number every time you build. But then you'll have a different problem: my sandbox and your sandbox will increment the serial number independently, oftentimes using colliding serial numbers. It would be a sandbox-specific serial number, hardly a good way to identify a build. You could build a shared database column with a globally incrementing serial number, but that's overkill.

Instead, use svnversion, a command line tool that returns the global version number of your repository. It is a stable, unique number that everyone can use, and it yields a true build identifier. At the command line, type svnversion, and you'll see your repository version number:
$ svnversion
22
To inject the svnversion number into your Android app's About screen, add a new resource to your strings.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, BuildNumberExample!</string>
    <string name="app_name">Build Number Example</string>

    <string name="app_svnversion">foo</string>
</resources>
Then, make sure you have an ant build script. I created my project using Eclipse, so I don't have a build script yet. Tell Android to create an ant build script for you:
$ android update project --path .
Now you have a build.xml file, and you can type things like ant clean, ant debug, and ant install to build and install your app. Test your build script to make sure it works:
$ ant clean
Buildfile: C:\usr\local\workspace\build-number-example\build.xml
    [setup] Project Target: Android 1.6
    [setup] API level: 4

clean:
    [delete] Deleting directory C:\usr\local\workspace\build-number-example\bin
    [delete] Deleting directory C:\usr\local\workspace\build-number-example\gen

BUILD SUCCESSFUL
Total time: 46 seconds

$ ant install
Buildfile: C:\usr\local\workspace\build-number-example\build.xml
    [setup] Project Target: Android 1.6
    [setup] API level: 4

-compile-tested-if-test:

-dirs:
    [echo] Creating output directories if needed...
    [mkdir] Created dir: C:\usr\local\workspace\build-number-example\gen
    [mkdir] Created dir: C:\usr\local\workspace\build-number-example\bin
    [mkdir] Created dir: C:\usr\local\workspace\build-number-example\bin\classes

-resource-src:
    [echo] Generating R.java / Manifest.java from the resources...

-aidl:
    [echo] Compiling aidl files into Java classes...

compile:
    [javac] C:\android-sdk-windows\platforms\android-1.6\templates\android_rules.xml:248: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Compiling 2 source files to C:\usr\local\workspace\build-number-example\bin\classes

-dex:
    [echo] Converting compiled files and external libraries into C:\usr\local\workspace\build-number-example\bin\classes.dex...
    [echo]

-package-resources:
    [echo] Packaging resources
    [aaptexec] Creating full resource package...

-package-debug-sign:
    [apkbuilder] Creating BuildNumberExample-debug-unaligned.apk and signing it with a debug key...
    [apkbuilder] Using keystore: C:\Documents and Settings\kasper\.android\debug.keystore

debug:
    [echo] Running zip align on final apk...
    [echo] Debug Package: C:\usr\local\workspace\build-number-example\bin\BuildNumberExample-debug.apk

install:
    [echo] Installing C:\usr\local\workspace\build-number-example\bin\BuildNumberExample-debug.apk onto default emulator or device...
    [exec] pkg: /data/local/tmp/BuildNumberExample-debug.apk
    [exec] Success
    [exec] 17 KB/s (13601 bytes in 0.781s)

BUILD SUCCESSFUL
Total time: 2 minutes 2 seconds

$
Add a new target to your build.xml file. This target executes svnversion and injects it into your strings.xml file.
<target name="foo-update-svnversion">
    <exec outputproperty="build.svnversion" executable="svnversion">
        <arg line="-n -c" />
    </exec>
    <property name="match.start" value="<string name="app_svnversion">"/>
    <property name="match.end" value="</string>"/>
    <replaceregexp file="res/values/strings.xml"
                   match="${match.start}.*${match.end}"
                   replace="${match.start}${build.svnversion}${match.end}">
    </replaceregexp>
</target>
At the command line, invoke the new ant target and then look at your strings.xml file. You'll see the svnversion number in strings.xml:
$ ant foo-update-svnversion
Buildfile: C:\usr\local\workspace\build-number-example\build.xml
    [setup] Project Target: Android 1.6
    [setup] API level: 4

foo-update-svnversion:

BUILD SUCCESSFUL
Total time: 2 seconds

$ cat res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, BuildNumberExample!</string>
    <string name="app_name">Build Number Example</string>

    <string name="app_svnversion">22</string>
</resources>

$
Now add some build targets to integrate your svnversion target with the default Android targets:
<target name="foo-debug" depends="foo-update-svnversion">
    <antcall target="debug"/>
</target>

<target name="foo-install" depends="foo-update-svnversion">
    <antcall target="install"/>
</target>
Finally, let's go back to your About screen. Add a method, aboutWithSvnVersion(), that grabs the svnversion resource and displays it on the screen:
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    View aboutView = aboutWithSvnversion();
    setContentView(aboutView);
}

View aboutWithSvnversion() {
    TextView aboutView = new TextView(this);

    String svnversion = getResources().getString(R.string.app_svnversion);
    aboutView.setText("Build number: " + svnversion);

    return aboutView;
}
Build your Android app by typing ant foo-debug or ant foo-install. This does exactly what you want, and now the world is safe again.

Resources:

3 comments:

Anonymous said...

Very helpful. Thank you

Ngoc Dao said...

You should put the custom target to custom_rules.xml. It is included by build.xml.

Richard Kasperowski said...

Ngoc, thanks for the great advice.

LinkWithin

Related Posts with Thumbnails