How do I build from a Git repository subdirectory with Jenkins & Ant?

4.4k views Asked by At

I have Jenkins CI configured to work with GitHub. It successfully populates my ${workspace} but the actual project I want to build is in a subdirectory of the repo:

https://github.com/androidfu/CodeExamples

Invoking Ant from within Jenkins states that it can't find build.xml. Well, that's because build.xml actually exists in ${workspace}/DeveloperDebugPreferences.

2

There are 2 answers

1
DoctorRuss On BEST ANSWER

You can configure the build file for Ant to use within Jenkins. If you are browsing to the job on your Jenkins server, you can choose Configure, then the Advanced option below your Ant target.

If you'd rather edit your config.xml file, you should see the build settings something like this:

<builders>
  <hudson.tasks.Ant>
    <targets>your_target_name</targets>
    <buildFile>${workspace}/DeveloperDebugPreferences/build.xml</buildFile>
  </hudson.tasks.Ant>
</builders>
0
Cameron Lowell Palmer On

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.