I wanted to take this a bit further and make a build system that started upon commit to Git, that would checkout the code, build it an publish it via SSH. In addition, I wanted the commit for the build tagged in the file name. The first step is to kick off the build process using post-receive in git and a locally installed Jenkins. If it was remote you would need to specify a URL and use wget or curl. For reference this is our Git branching model.
Git post-receive
# This must be read using read because the values are passed on
# STDIN not as command-line arguments.
read OLDREV NEWREV REFNAME
BRANCH=${REFNAME#refs/heads/}
. /usr/share/doc/git-core/contrib/hooks/post-receive-email
if [ ${BRANCH} == "master" ]; then
/usr/local/bin/jenkins-start "Our Project - Android - Releases"
elif [ ${BRANCH} == "develop" ]; then
/usr/local/bin/jenkins-start "Our Project - Android - Development"
fi
Jenkins
The Development side of things is basically identical to Releases, but instead of master we build on a commit to the develop branch. Since this server just hosts git and Jenkins and the Eclipse and Xcode environments are installed on a Mac Mini we use as a build server Jenkins is configured to use SSH with certificates for login. The Jenkins server SSHs to the build server, checks out the code, builds the code, and SSHs the result to our software shelf. The only special part is the Ant build.xml part that makes this work.
Under Build - Execute Shell - Command we put
~/android-sdks/tools/android update project -p "$WORKSPACE/OurProject"
Under Invoke Ant
-buildfile "$WORKSPACE/OurProject/build.xml" clean debug
ant.properties
This is where we place the information about the keystore for signing the binary.
key.store=ourProject.keystore
key.alias=release
key.store.password=aBigSecret
key.alias.password=aBigSecret
build.xml
So in the project build.xml we override a couple of targets to get the git commit hash to appear in the file name. For example, OurProject 1.0.0 (deadbeef)-release.apk. After this is built Jenkins copies the file over to our Software Shelf.
<target name="-set-debug-files" depends="-set-mode-check">
<exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
<arg value="rev-parse"/>
<arg value="--short"/>
<arg value="HEAD"/>
</exec>
<xpath input="AndroidManifest.xml"
expression="/manifest/@android:versionName"
output="android.app.version.name"
default="Unknown" />
<property name="out.packaged.file" location="${out.absolute.dir}/${ant.project.name}-debug-unaligned.apk" />
<property name="out.final.file" location="${out.absolute.dir}/${ant.project.name}-${android.app.version.name} (${git.revision})-debug.apk" />
<property name="build.is.mode.set" value="true" />
</target>
<target name="-set-release-mode" depends="-set-mode-check">
<exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
<arg value="rev-parse"/>
<arg value="--short"/>
<arg value="HEAD"/>
</exec>
<xpath input="AndroidManifest.xml"
expression="/manifest/@android:versionName"
output="android.app.version.name"
default="Unknown" />
<property name="out.packaged.file" location="${out.absolute.dir}/${ant.project.name}-release-unsigned.apk" />
<property name="out.final.file" location="${out.absolute.dir}/${ant.project.name}-${android.app.version.name} (${git.revision})-release.apk" />
<property name="build.is.mode.set" value="true" />
<!-- record the current build target -->
<property name="build.target" value="release" />
<property name="build.is.instrumented" value="false" />
<!-- release mode is only valid if the manifest does not explicitly
set debuggable to true. default is false. -->
<xpath input="AndroidManifest.xml" expression="/manifest/application/@android:debuggable"
output="build.is.packaging.debug" default="false"/>
<!-- signing mode: release -->
<property name="build.is.signing.debug" value="false" />
<!-- Renderscript optimization level: aggressive -->
<property name="renderscript.opt.level" value="${renderscript.release.opt.level}" />
<if condition="${build.is.packaging.debug}">
<then>
<echo>*************************************************</echo>
<echo>**** Android Manifest has debuggable=true ****</echo>
<echo>**** Doing DEBUG packaging with RELEASE keys ****</echo>
<echo>*************************************************</echo>
</then>
<else>
<!-- property only set in release mode.
Useful for if/unless attributes in target node
when using Ant before 1.8 -->
<property name="build.is.mode.release" value="true"/>
</else>
</if>
</target>
Software Shelf
This is just a publicly accessible webpage where a PHP script displays the builds in date order.
<?php
require('header.html');
?>
<h2>All builds</h2>
<table>
<?php
$dir = dirname($_SERVER[SCRIPT_FILENAME]);
$filenames = scandir($dir);
$files = array();
$fileTimes = array();
$j = 0;
$n = count($filenames);
for ($i = 0; $i < $n; $i++) {
$filename = $filenames[$i];
if ( is_file($filename) && pathinfo($filename, PATHINFO_EXTENSION) == "apk" ) {
$time = filemtime($filename);
$files[$j] = array("name"=>$filename, "time"=>$time );
$fileTimes[$j] = $time;
$j++;
}
}
array_multisort($fileTimes, SORT_DESC, $files);
$tablerow_classes = array("t0", "t1");
$current_class = 0;
$m = count($files);
for ( $i = 0; $i < $m; $i++ ) {
$name = $files[$i]["name"];
$time = date ("d/m/Y H:i:s", $files[$i]["time"]);
$class = $tablerow_classes[$current_class];
$current_class++;
if ( $current_class > 1 ) {
$current_class = 0;
}
echo "<tr class=\"$class\"><td><a href=\"$name\">$name</a></td><td>$time<br /></tr>";
}
?>
</table>
<?php
require('footer.html');
In summary, this system provides a complete build system and it allows you track a specific binary back to a specific commit.