Continuous Integration with PHP and Hudson part 1

I’m a big fan of Continuous Integration (CI), however I find the complexity of solutions such as phpUnderControl/CruiseControl off putting and disruptive. Setting up projects requires shell access to the server and therefore knowledge of the file system layout and every config change requires CruiseControl to be restarted for the changes to take affect. This just provides non-believers with more excuses to avoid unit testing.

I was introduced to Hudson during a Sheffield Java User Group meetup in late 2009. Inspired by the claims of easy web based configuration and a wide variety of plugins I had to give it a try. Over a couple of blog posts, I’ll cover the integration of PHP with Hudson using the following tools:

Create Ant build file

First step is a create an build file. This is a common task for most, if not all CI software. It provides the necessary glue between your application and the CI software by describing how to build your application in a series of steps known as targets. For the purposes of this example, I’m going to use a simple Calculator application.

In the top level directory of the application, create a file name build.xml and copy the following into it. Here a new project is defined, a few standard properties (variables) are setup and two targets are created, clean and prepare. The directories these targets refer to will be used by pdepend, etc to store their respective log files. Notice the default is set to build, we’ll add this last.

<project name="Calculator" default="build">
    <property name="dir.src" value="${basedir}/application" />
    <property name="dir.tests" value="${basedir}/tests" />
    <property name="dir.build" value="${basedir}/build" />
    <property name="dir.logs" value="${dir.build}/logs" />
    <property name="dir.docs" value="${dir.build}/docs" />
    <property name="dir.coverage" value="${dir.build}/coverage" />

    <target name="clean">
        <delete dir="${dir.build}"/>
    </target>

    <target name="prepare">
        <mkdir dir="${dir.logs}"/>
        <mkdir dir="${dir.logs}"/>
        <mkdir dir="${dir.docs}"/>
        <mkdir dir="${dir.coverage}"/>
    </target>

Now we add a target per PHP tool that we want to ran as part of our build process. First start with PHP Depend, note how the properties defined above are used to configure where the XML log file is written to and where to find the application code directory.

    <target name="pdepend">
        <exec dir="${basedir}" executable="pdepend" failonerror="true">
            <arg line="--jdepend-xml='${dir.logs}/jdepend.xml'
                       --bad-documentation '${dir.src}'" />
        </exec>
    </target>

And a target for phpcpd,

    <target name="phpcpd">
        <exec dir="${basedir}" executable="phpcpd" failonerror="true">
            <arg line="--log-pmd '${dir.logs}/pmd.xml' '${dir.src}'" />
        </exec>
    </target>

And PHP CodeSniffer. I’ve chosen to you use the Zend coding standards, change this to which ever standard you prefer.

    <target name="phpcs">
        <exec dir="${basedir}" executable="phpcs" output="${dir.logs}/checkstyle.xml" failonerror="false">
            <arg line="--report=checkstyle
                       --standard=Zend
                       '${dir.src}'" />

        </exec>
    </target>

phpDocumentor,

    <target name="phpdoc">
        <exec dir="${basedir}" executable="phpdoc" failonerror="true">
            <arg line="-q -ue on
                       -t '${dir.docs}'
                       --directory '${dir.src}'
                       -ti '${ant.project.name}'
                       --parseprivate on
                       --undocumentedelements on
                       --output HTML:Smarty:PHP" />
        </exec>
    </target>

PHPUnit,

    <target name="phpunit">
        <exec dir="${basedir}" executable="phpunit" failonerror="true">
            <arg line="--log-junit '${dir.logs}/junit.xml'
                       --coverage-clover '${dir.logs}/clover.xml'
                       --coverage-html '${dir.coverage}'
                       '${dir.tests}'" />
        </exec>
    </target>

Last but not least, the build target. As the default target, this will be execute if no target is specified when running Ant. Finally the project tag is closed. This complete the build file.

    <target name="build"
            depends="clean, prepare, phpdoc, phpcpd, pdepend, phpcs, phpunit" />
</project>

To test switch to the directory your build.xml is in and just type ant. You should get something like,

$ ant
Buildfile: /home/simon/git/Calculator/build.xml

clean:
   [delete] Deleting directory /home/simon/git/Calculator/build

prepare:
    [mkdir] Created dir: /home/simon/git/Calculator/build/logs
    [mkdir] Created dir: /home/simon/git/Calculator/build/docs
    [mkdir] Created dir: /home/simon/git/Calculator/build/coverage

phpdoc:

phpcpd:
     [exec] phpcpd 1.3.1 by Sebastian Bergmann.
     [exec]
     [exec] 0.00% duplicated lines out of 27 total lines of code.

pdepend:
     [exec] PHP_Depend 0.9.9 by Manuel Pichler
     [exec]
     [exec] Parsing source files:
     [exec] .                                                                1
     [exec]
     [exec] Executing Dependency-Analyzer:
     [exec]                                                                  4
     [exec]
     [exec] Generating pdepend log files, this may take a moment.
     [exec]
     [exec] Time: 00:00; Memory: 4.25Mb

phpcs:

phpunit:
     [exec] PHPUnit 3.4.11 by Sebastian Bergmann.
     [exec]
     [exec] ..
     [exec]
     [exec] Time: 1 second, Memory: 6.00Mb
     [exec]
     [exec] OK (2 tests, 2 assertions)
     [exec]
     [exec] Writing code coverage data to XML file, this may take a moment.
     [exec]
     [exec] Generating code coverage report, this may take a moment.

build:

BUILD SUCCESSFUL
Total time: 5 seconds

Targets can be run individually or chained together by specifying their names as parameters to ant. So for example, “ant phpdoc” or “ant clean prepare phpunit“.

To conclude, we have automated the build process by creating an Ant build file. With a little one-time effort, every developer in the team can now easily and quickly the status of the application after making changes to ensure nothing has broken, just by typing a three little command! The code for this sample application is available on GitHub. Next we’ll use Hudson to automatically run the build after every commit, track changes over time and name ‘n shame those who brake the build.

~ by Si on March 22, 2010.

Leave a comment