PHP Continuous integration, with Jenkins and Phing
This article is about how to use Phing in your projects, so a continuous integration server (in this case Jenkins -ex Hudson-) can generate the necessary artifacts for your php application (deployment artifacts, documentation, code metrics, etc). I'll try to show why this will make your life easier when developing or auditing code, generating releases and deploying new versions, trace bugs, etc. All with just a handful of phing tasks.
Note that I wont go into any details about what is CI and what it implies to your way of work. I'll assume you already heard about it, and you just want to implement it for your php projects. To know more about continuous integration, you can read this post by Martin Fowler and this wiki post.
Phing in PHP is like ant in java, and "kind of" like make. For an in depth tour of phing, you can read the user guide. I'll describe a skeleton of an application, that you could easily copy&paste to port legacy projects (or create new ones) so that they are ready to be built by Jenkins (and also, the needed job configuration). Note that except for the job configuration (which is jenkins specific), everything can be used in the CI server of your choice.
Not only that, but also your team members can manage your project (their copies) by calling the same phing tasks. So humans can run tests, see coverage and other metrics before commiting, and also try the artifacts that will be generated when the CI server builds them. This will surely rise the quality of the commited code and also yield some very nice statistics about the code itself.
Also, the guys from "operations" will be happy to be automatically notified by email when a new build is made, so they can upgrade any production code. They just need to download the latest build from the CI server and do their thing. Also, the project will have a lot more visibility to the outside world.
Another nice feature shown in the example code is how to create an isolated environment where you can test new dependency versions without affecting other users or other copies of the code. Every member of the team have their own versions of the code and the dependencies.
In the end, you will have a collaborative environment where code can be reviewed and enforced to comply to specific development rules, releases are automatically generated, and other people can easily access your deployment artifacts and documentation. Just commit, and everything will be automagically built.
First, a couple of useful links:
- Code for this article @Github: Example application for this article, including unit tests, ready to go to a CI server of choice.
- Jenkins Job: The resulting job built from the above application.
- My Jenkins on PaaS refcard.
- Jenkins CI Server: The CI server chosen (another good one is CruiseControl)
- Phing: This is the tool chosen to build and manage the project.
- PHPUnit: This provides the unit tests framework.
- CodeSniffer: Provides syntax check for the project.
- PHPMD: Detects possible problems and complexity throughout the code.
- PHPCpd: Copy&Paste code detector :)
- PHPLoc does a summary of how much lines of code, classes, etc the code has.
- PHPDepend is used to analyze dependencies between the different modules of the application.
- The DocBlox Project used to generate API documentation.
- CI Post by Martin Fowler.
- Phing user guide
You will need to have already installed:
- Jenkins (here are the installation instructions.)
- PHP (with a working copy of pear)
- Phing (installation instructions.)
- Xdebug so PHPUnit can generate the coverage report.
- The phing plugin for Jenkins.
Download the example code:
$ git clone git://github.com/marcelog/Ci-Php-Phing-Example.git $ cd Ci-Php-Phing-Example
Configuring the build
Before running anything, we need to configure the current php and pear copies in the project. Another properties file is used in this case, named php.properties, which is shipped as an example, and ignored at the scm level. This is so you can customize this file without having to commit this change and affect everyone else. So:
Ci-Php-Phing-Example $ cp php.properties.example php.properties
Edit the file php.properties, configure the property "php.bin", "php.ini", and "defaultpear.bin" with the path for your own. You can find a sample php.ini file in resources/php.ini.example. But maybe you should just copy and reuse the one in your system.
Now try the install-dependencies target:
Ci-Php-Phing-Example $ phing install-dependencies
It may take a while to complete, but you only need to run this when checking out the project or when upgrading/adding a dependency. This will create a "vendor" directory, and install a fresh copy of pear. This copy of pear will be used to download a new version of phing, phpunit, phpcpd, phpmd, pdepend, etc. So now you have your own versions of these tools (and so everyone in your team). These wont conflict with each other or with the defaults installed in the dev server you're using.
- clean: Will remove the build directory, where artifacts are generated.
- report: Will create all the reports from the code metrics tools.
- package: Generates phar and pear package distribution.
- test: Will run the unit tests and produce coverage output.
- all: Will run report and package targets.
When running something like "phing test", the phing that is run, is the one installed in the dev server you're using. This copy of phing will then be used internally to use the phing copy inside of the "vendor" directory. This gives you the possibility of changing the phing version without affecting the one installed in the system. Same thing applies to the pear copy used and everything inside the vendor directory.
The build output
The artifacts are generated in the "build" directory, which is created automatically from the build.xml file. After a complete build, you will find the phar distribution, the pear package, and the xml and html's generated by the tools.
That's all. Give it a try, and then continue reading :) Now you and your team members have a common way of managing their copies of the project without affecting each others work.
Tools and Software Metrics Report
The following tools are installed by the "install-dependencies" target and are used to generate lots of software metrics reports. These reports will then be published by the CI server.
- Codesniffer: CodeSniffer enforces the use of a coding standard. It will warn you when a code does not follow the given standard (for example, putting braces in a new line when writing for's, if's, etc or using tabs instead of spaces). It's very useful when you have new team members or lots of people making commits, and you want to make sure that everyone writes the code in the same way.
- PHPMD: PHPMD is the PHP Mess Detector. Will issue warnings when it detects that a code violates some well known best practices or smells in some way, like classes containing too many methods or properties, code that exceeds certain CRAP, NPATH, etc. You can see a complete list of these (and their meanings) here.
- PHPCPD: The PHP Copy&Paste detector. Will analyze the code trying to find any copy pasted code and warn you if any is found.
- PDepend: Will generate a report for package dependencies in the project, will report any cyclic dependencies.
- PHPLoc: Measures project size in lines of code, classes, methods, etc.
- DocBlox: Generates API documentation from code.
Two kind of distributions are made in this example application:
- Phar file: Generated by resources/generatePhar.php.
- Pear package: Generated by resources/generatePackageXml.php. Actually this file generates the package.xml file needed by the pear command "pear package". You can easily create a pear channel with Pirum if you want to distribute it.
The Resources directory
The resources directory of the project contains the following needed stuff:
- checkstyle.xsl / cpd.xslt / pdepend.xsl / phpunit_to_surefire.xslt / pmd.xslt: I've collected these stylesheets from different sources, they were not created by me, and unfortunately I've lost the original author(s) and website(s) for them. They are needed so Jenkins can correctly publish the results for the tools CodeSniffer, PHPCPD, PHPMD, PHPDepend, and phpunit. Jenkins can publish the results of their Java equivalents, but a few transformations are needed to make the PHP counterparts fully compatible.
- php.ini.example: An example php.ini file. This one will be used to invoke the tools, so you can run them in a "controlled" environment.
- generatePhar.php: A very simple script used to generate a phar distribution for the entire project.
- generatePackageXml.php: This is a very (very) raw script that I wrote in a few minutes so I can easily generate the package.xml file needed for pear, so then a "pear package" will create the pear package in .tgz form.
As you can see in the example configuration for the Jenkins job (the image at the end of this article), I've set that the git repository should be polled for changes every 5 minutes (a cron-like syntax). When Jenkins detects a new commit, it will build a new release.
Of course, you can manually trigger the builds whenever needed.
The release versions
By default, Jenkins increments the build number for every new build. Starting at 1. So the next version is 2, then 3, and so on.
I like version numbers to be something like 1.3 or 2.8, so for a more advanced behavior when creating a version number for a build, I'm using the Version Number plugin and the Jenkins environment variables.
I've configured the job to generate this "formatted version number" (check the image with the example configuration), composing a major version number (in this case, "1"), a dot, and the build number. So the first version number will be 1.1, then 1.2, 1.3, etc. This version number will be stored in an environment variable (APP_VERSION) before running the build.
NOTE:I could not manage to get the build version out of a git tag (in a simple way), which would've be better.
Note that this behavior is enough for my own projects, but at work, we update the major version every 10 sprints. We also have a minor version number, that is the number of the sprint since the beginning of that version, and a micro version which is the build number inside that sprint, and is reset to 0 in every sprint beginning. So a version like 2.3.12 means the product version number 2, 3rd sprint for that version, and build 12th since the beginning of the sprint.
Also, I've added the GIT_COMMIT variable value to the pear package description and the name of the phar distribution. This is populated by Jenkins with the id the commit that triggered the build.
Setting up Jenkins
Here's an example of how to configure Jenkins to generate and publish the artifacts (you can click the image to see it completely). After each build, you will get the software metrics published and also a phar and pear distribution.
In the image, the full configuration is shown. However, the important stuff is how to configure the repository polling, the application version number, the phing targets to run, and the publishing of the artifacts. In order to use phing in Jenkins, you need the phing plugin.
Configure the job and trigger a build. The 1st build will fail because php.properties is missing, so get into the jenkins workspace and setup the configuration as we did above. Then trigger a new build, which should be successfull.
Note: In the image, the paths for the publication of html artifacts are not correctly shown, so here they are:
- CPD Report: build/php-cpd/html
- PHPDepend Report: build/php-depend/html
- PHPLoc Report: build/php-loc/html
- Coverage Report: build/php-unit/html
- PHPDepend dependency graph: build/php-depend/html
- PHPDepend pyramid graph: build/php-depend/html