PHP
Article

Preparing and Building a PHP Project in Jenkins

By Peter Nijssen

PHP Quality Assurance with Jenkins

In a previous article, we went through the installation of Jenkins and prepared everything to get started. We will continue in this article by actually preparing our project. If you have a Jenkins setup ready from before, no need to go through the previous part – you can follow along with this one quite nicely.

Prepare composer

We are going to prepare our project first. We could use the project supplied by Sebastian Bergmann, but it’s rather a basic project and will run without problems in Jenkins. In this article, we will be using a different repository in which we will have to overcome some hurdles. We will be using a fork of Jumph as our basis.

First and foremost, we have to make sure that we have tools like PHPUnit and PHP-CodeSniffer available. We can do this in two different ways. Either we install the packages on our Jenkins server through Pear or Composer globally, or we define the dependencies in our composer.json file. I decided to go with the latter method, so we can easily control the version of these tools on our own local machine as well as on Jenkins. So we start by adding the following lines to the composer.json file and run composer update.

"require-dev": {
    "squizlabs/php_codesniffer": "~1.5.*",
    "phpmd/phpmd": "~2.1.*",
    "sebastian/phpcpd": "~2.0.*",
    "pdepend/pdepend": "~2.0.*",
    "phploc/phploc": "~2.0.*",
    "phpunit/phpunit": "~4.3.*",
    "theseer/phpdox": "~0.7.*",
},

If you want to know more about what each package does, have a look at Bruno’s article about PHP QA tools.

Prepare Ant

On our Jenkins server, we will be using Ant. So in case Ant is not yet installed on your server, make sure it is by running the following command.

sudo apt-get install ant

Ant helps you automate the software build process. You define tasks, called targets, in the configuration file. Ant will read this file and perform the appropriate action. You can define dependencies to indicate in which order Ant should perform these tasks. The configuration file is the so called build.xml file. We are going to add this to our project, so Ant can perform these tasks on our Jenkins server. We are going to use the template provided by Sebastian Bergmann which can be found here. We add this to the root of our project.

To run Ant, you can kick off ant build on the command line. This means that Ant will run the build target. If no parameter is given, Ant will run the target as indicated in the configuration as default, which in this case is also build.

Let’s take a closer look at the build target.

<target name="build" depends="prepare,lint,phploc-ci,pdepend,phpmd-ci,phpcs-ci,phpcpd-ci,phpunit,phpdox" description=""/>

The target is empty, however, it depends on a lot of other targets. It will first run the prepare target and then continue with the lint target until every target has been run. Let’s have a look at the phploc-ci target for example.

<target name="phploc-ci" depends="prepare" description="Measure project size using PHPLOC and log result in CSV and XML format. Intended for usage within a continuous integration environment.">
    <exec executable="${toolsdir}phploc">
        <arg value="--count-tests"/>
        <arg value="--log-csv"/>
        <arg path="${basedir}/build/logs/phploc.csv"/>
        <arg value="--log-xml"/>
        <arg path="${basedir}/build/logs/phploc.xml"/>
        <arg path="${basedir}/src"/>
        <arg path="${basedir}/tests"/>
    </exec>
</target>

This target is fairly easy to understand. You will notice it depends on the prepare target. Since that was already run by the build target, it won’t run again. Then we get the executable, which is phploc, which will be executed by Ant. You installed this earlier with Composer. Lastly, we will see the PHPLOC specific arguments. Ant will run the following command line input based on this target configuration.

phploc --count-tests --log-csv /build/logs/phploc.csv --log-xml /build/logs/phploc.xml /src /tests

The ${toolsdir} and ${basedir} are 2 variables. Depending on how they are configured, they might have an effect on the overall command line output

As indicated above, if you only wanted to trigger this target, you should run ant phploc-ci on the command line.

Notice that this PHPLOC command will output a phploc.csv file and a phploc.xml file. The plugins installed in Jenkins earlier will read these files and convert them to graphs, for example. In the next article we will dive deeper into this topic.

If you look through the whole build.xml file, you will notice that it’s depending on 3 other separate config files for certain tools; build/phpmd.xml, build/phpdox.xml and build/phpunit.xml. These configuration files are requested by the actual tools. To find out more, look at their respective docs.

Note that phpunit.xml is heavily inspired by the original phpunit.xml.dist file from Symfony2 since we are dealing with a Symfony2 project.

Changing the build.xml file

So, are we done? Unfortunately, not yet. The build.xml file is very generic and not completely suited for our project. First we need to define where tools like PHPUnit can be found. Our composer installed these automatically in the bin directory in the root of our project. We need to change the toolsdir property to the correct value.

<property name="toolsdir" value="bin/" />

We can also clean up several commands. The current build.xml file defines that we on one hand have a src directory and on the other hand a tests directory. Since Symfony2 includes tests in the src directory, we can remove all paths to the tests directory like this example.

<arg path="${basedir}/tests"/>

Although it’s not required, I prefer to keep everything as clean as possible so it matches our project correctly.

Lastly, the current configuration for PHPDox as indicated in the build.xml file while writing this article does not work with the latest version in my case. I changed the target like below, so it correctly reads our configuration file.

<target name="phpdox" depends="phploc-ci,phpcs-ci,phpmd-ci" description="Generate project documentation using phpDox">
  <exec executable="${toolsdir}phpdox">
   <arg value="--file" />
   <arg value="${basedir}/build/phpdox.xml" />
  </exec>
 </target>

Creating new targets

Everything has been set up to make sure all our QA tools will run as expected. However, our project is just a bit more complicated. If you analyzed the repository, you will notice that dependencies are regulated by Composer and Bower. Since it’s a Symfony project, we will also need a default parameters.yml file. We need to deal with these requirements to be able to set up the project correctly on Jenkins.

Let’s start off with the parameters.yml file. We can work with the default content of parameters.yml.dist, so we are just going to copy that file. We start by creating a new target named copy-parameters which executes the cp command.

<target name="copy-parameters" description="Copy parameters.yml file">
  <exec executable="cp" failonerror="true">
   <arg path="app/config/parameters.yml.dist" />
   <arg path="app/config/parameters.yml" />
  </exec>
 </target>

Next up are Composer and Bower. I decided to install these globally on the Jenkins server by executing the following commands.

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/bin/composer

sudo apt-get install nodejs
sudo apt-get install npm
sudo npm install -g bower
sudo ln -s /usr/bin/nodejs /usr/bin/node #This is needed on certain linux distro's.

[Ed: Note that if you’re running all this in a VM hosted on Windows, BowerPHP will be a much better option than Bower.]

Now lets create a target for Composer.

<target name="composer" description="Installing composer dependencies">
 <exec executable="composer" failonerror="true">
  <arg value="install" />
  <arg value="--dev" />
  <arg value="--prefer-dist" />
  <arg value="--no-progress" />

  <env key="SYMFONY_ENV" value="test"/>
 </exec>
</target>

It’s important to set SYMFONY_ENV to test so symfony knows it has to clear its test cache directory.

Let’s continue with Bower.

<target name="bower" description="Installing bower dependencies">
 <exec executable="bower" failonerror="true">
  <arg value="install" />
 </exec>
</target>

The final thing we need to do, is to make sure these targets are executed. So we change the build target and add the 3 newly created targets in the order we want to execute them.

<target name="build" depends="prepare,copy-parameters,bower,composer,lint,phploc-ci,pdepend,phpmd-ci,phpcs-ci,phpcpd-ci,phpunit,phpdox" description=""/>

Git

The last thing we need to do is to prepare our .gitignore file. If you ever want to run an Ant target on your local machine, your build directory will be filled with log files which you don’t want to commit to your repository. So we add the following lines to the .gitignore file.

# build
!build/phpmd.xml
!build/phpunit.xml
!build/phpdox.xml
build/*

Perhaps you got a little confused by all the changes we made. For your convenience, here is a direct link to the commit with all the changes we described above.

Create the project in Jenkins

Our project is ready. Let’s head back to Jenkins and configure it. This will be the easiest part in this article.

Since our project is located on Github, I suggest you install the Github plugin within Jenkins. If you are using your own git server, you can just install the regular Git plugin. If you are uncertain how to install a plugin, check the previous article.

When you return to the overview, you have to click new item in the left menu. As item name, we fill in Jumph. Next, we choose the option copy existing item and fill in php-template as the project to copy from.

Jenkins

We will immediately be taken to the configuration page of this project. First we uncheck Disable build so our build will be enabled on save. Within the Github project input field, we fill in the URL to the Github project.

The last thing we have to do is choose the source management. In our case, this is git. Next, you have to fill in the repository link. If you use the HTTPS link, you don’t have to provide any additional credentials. We can decide which branches should be build, but since we only have a master branch, we will leave it as it is.

The configuration is done – it’s so easy because we used a predefined template. If you scroll down, you will exactly see what the template configured for us. Don’t forget to save your configuration before continuing.

Start a build

If you followed all the steps so far, you should now be able to start a build. You can start a build by clicking build now in the left side menu of a project. You will notice at the bottom left table that a build appears.

Jenkins

If you click on the build itself, you will get a new side menu with options for this particular build. Click on console output to see exactly what’s going on. In my case, the first build failed due to missing some PHP extensions.

Jenkins

The console output comes in very handy for finding out why your project is failing. In my case, I just had to run sudo apt-get install php5-curl php5-xsl php5-sqlite php5-xdebug to install some needed packages. It took me a couple of builds to figure out what was missing, but with the console output, it’s easy to figure out what is going wrong. In the end, this is what my build history looks like.

Jenkins

Red means the build has failed, where blue means the build was successful. A failure can mean something went wrong with the configuration as we saw before, but it could also mean that unit tests have failed. If you open up your build.xml file again, you will notice that some targets got an attribute named failonerror which is set to true. If for some reason, that target fails, Ant will automatically stop and will report the build as broken.

Perhaps you expected a green ball instead of a blue ball when a build succeeds. There is a plugin available to show green balls instead of blue ones.

Conclusion

In this article, we worked our way to a successful build. We had to prepare our project and create a job within Jenkins. Finally we had to debug our first couple of builds since we forgot to install some libraries.

In the next part, we will take a look at all the results Jenkins gives back.

  • http://www.fadelchafai.com/ Fadel

    Thanks Peter, great tutorial. It helped me a lot
    it would be better if it was more generic instead of using Symfony ;)

    • https://www.peternijssen.nl/ Peter Nijssen

      Thanks! I believe 99% of the article is generic. The only symfony parts I see are the paths and the parameter file. You can easily fill in what you prefer :)

      • http://www.fadelchafai.com/ Fadel

        Not that, Im a Zend Framework fun :D

  • http://it451.com Daniel

    Thanks Peter, this series of tutorial is great! I just have a question, the project is to be created under the same folder we created on the previous article called php-template under the jenkins home folder?

    • https://www.peternijssen.nl/ Peter Nijssen

      Hi Daniel. Thanks! I am not sure if I understand you correctly. You just need to prepare your PHP project by doing the steps within this article. In the end, you create a new job in jenkins based on the php-template. In the end, the php-template is just a jenkins template so you don’t have to configure all metrics by hand.

      • http://it451.com Daniel

        Hi Peter, thanks for your reply. I was modifying the build.xml under the php-template folder and I got confused when I got to the part where I was suppose to modify the .gitignore as there is not such file on the php-template folder. I kept going and later on I understood and was able to run a successful build from your Jumph repository. I guess you expected us to fork your repository?

        • https://www.peternijssen.nl/ Peter Nijssen

          Hi. No need indeed to modify the php-template. You can use Jumph as an example, but in the end, you probably want jenkins to run your own project. I hope these articles shed a light in how you can do this. Basically you have to perform all these actions on your own project and use what you want and leave out what you don’t need :)

  • http://www.anvweb.com/ Vytautas

    Is there any good reason why are you using Ant instead of Phing? IMHO Phing is better solution for PHP projects deployment. At first you are staying inside the PHP ecosystem, also you can easily extend Phing with your own tasks with PHP, etc.

    • https://www.peternijssen.nl/ Peter Nijssen

      Thanks for your response. I basically have two reasons. The first reason is that these articles are based on Jenkins template for PHP (http://jenkins-php.org/). I think that website is a handy start point for many developers who start with Jenkins and PHP. I could have written everything in Phing (or even in bash), but then I believe the article would be too big. That’s why I rely on the ant tasks generated already. A separate article about Phing however would be nice of course.

      The second reason is that Ant is a more widespread tool among Jenkins users and if you are using Jenkins to build more then just PHP projects, you probably need to get along with Ant. So from an “educational” point of view, Ant is the more logical choice. In the end, Phing is also based on Ant.

      Additionally, I don’t believe staying in the PHP ecosystem is always an advantage. If I would have done that, I would have used PHP-CI (http://www.sitepoint.com/continuous-integration-php-ci/) but I still believe Jenkins is superior.

      I guess it’s also a matter of preference and were you come from :)

  • jerrac

    I finally had time to work on setting up a php project in my Jenkins install. I mostly followed what you wrote, just with my own project, not Jumph. And the code is on our GitLab server, not GitHub. I keep running into this error though:

    Publishing Clover coverage report…
    Clover xml file does not exist in: /var/lib/jenkins/workspace/myproject called: build/logs/clover.xml and will not be copied to: /var/lib/jenkins/jobs/myproject/builds/15/cloverphp/clover.xml
    Could not find ‘build/coverage/build/logs/clover.xml’. Did you generate the XML report for Clover?

    Google hasn’t been any help yet. Any suggestions?

    • https://www.peternijssen.nl/ Peter Nijssen

      Well, it’s hard to tell. The first thing to notice is that it didn’t find a file called clover.xml. This file is being generated by phpunit if I am not mistaken. What I normally do is, is to run the command manual on the jenkins server as the jenkins user. So I log in to the server, log in as user jenkins, go to the workspace and run the command manually (for example: phpunit -c config.xml). In general, you will get an error which will help you on your way.

      However, firstly you could also check the console log in the project in Jenkins it self. In most cases, it already says something.

      I think your unit tests crash rather then fail or succeed.

      • jerrac

        One issue was I hadn’t created the php*.xml files in the build directory.

        The rest seems to be because I don’t have any php unit tests to run, clover.xml, crap4j, etc had nothing to do. And instead of just skipping those tasks, it errored on me.

        Hmm…. One of these days I’ll have time to do TDD… Heh.

        Anyway, thanks for the response. :)

  • jerrac

    Here’s another question. How do you deal with running tests that require the application be installed with a database backend? For example, Behat or SimpleTests on a Drupal 7 site.

  • Eduardo Iriarte

    Im just overflying your text, but i didnt saw any configuration for “deployment”…

  • http://books.aucklandwhich.org A. Datta

    Hello,
    I am a LAMP developer learning Docker, am halfway into Turnbull’s Docker book and think will dive into Jenkins next. My main concern is that I do not know Java or have the time or inclination to learn it (read I want to avoid Java PROGRAMMING now). Can I learn and use Jenkins with PHP without learning Java programming?
    Thanks

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.