L’analyse statique d’un projet PHP par une PIC telle que Jenkins nécessite une optimisation des règles d’exclusion des répertoires ne faisant pas partis du code développé. Vous verrez quelques astuces pour accélérer l’intégration continue d’un projet PHP.
Les outils d’analyse statique
J’ai récemment repris la maintenance d’un projet PHP open source et je souhaitais l’intégrer dans une PIC (Plateforme d’Intégration Continue) afin d’obtenir des indicateurs sur la qualité du code. Ces indicateurs ne sont pas produits par Jenkins lui-même, mais par des outils tiers (pour la plupart, c’est un portage – pour le PHP – d’outils d’analyse provenant du monde Java) tels que :
- pdepend pour pour des statisques sur la qualité du code (complexité cyclomatiques, profondeur d’héritage, nombre de méthodes surchargées…). Cet utilitaire est basé sur JDepend.
- phpmd pour la détection de certains bugs, de paramètres, méthodes ou propriétés non utilisées, etc
- phpcpd pour la détection du code dupliqué (par copié/collé).
- phploc pour mesurer rapidement la taille et la structure d’un projet PHP.
- phpcs pour https://github.com/squizlabs/PHP_CodeSniffer (voir aussi la documentation du package PEAR).
- phpdoc pour la génération de la documentation à partir des commentaires du code (similaire à Javadoc).
- phpcb pour générer une vue stylée du code avec la mise en évidence des alertes remontées par les outils précédents. Cela génère un site en HTML contenant le code de l’application.
Ces utilitaires peuvent être utilisés en dehors de la PIC pour vérifier tel ou tel aspect. Il existe d’autres utilitaires PHP d’analyse de code comme PhpCheckstyle ou Yasca (audit de sécurité). Le but de ce post n’est pas de parler des autres outils intégrables à la PIC (phpunit, selenium…), mais de l’optimisation des outils de contrôle précités qui s’avèrent très gourmands en ressources système.
L’absence de règles d’exclusion met la CPU au supplice
Le problème est que les applications PHP modernes s’appuient sur des frameworks PHP (on peut citer Zend, Symfony, cakePHP…), Javascript (Dojo, JQuery…) et des composants tiers (diverses bibliothèques telles que PHPExcel, PHPWord, pChart) voire des sous-systèmes (CMS Drupal ou Joomla). Ces applications peuvent peser 100 à 200Mo avec plusieurs dizaines ou centaines de milliers de fichiers.
Le but n’étant pas d’obtenir des indicateurs de qualité de ces produits, mais de votre logiciel il faut exclure les fichiers sources de votre analyse. Sans quoi, chaque vérification mettra 24 à 48h sur un PC moyen (sans blague, on a fait le test au boulot).
Les règles d’exclusion
Voici le détail des règles d’exclusion outil par outil qui part du principe que le répertoire courant (.) contient les sources à analyser. Vous trouverez un exemple de fichier build.xml (pour intégration dans Jenkins) vers la fin de l’article.
pdepend
http://pdepend.org/documentation/handbook/command-line.html
pdepend --jdepend-xml=./build/logs/jdepend.xml --jdepend-chart=./build/pdepend/dependencies.svg --overview-pyramid=./build/pdepend/overview-pyramid.svg --ignore=library/,tests/,utils/,build/,js/,drupal/,bin/ --suffix=php .
phpmd
http://phpmd.org/documentation/index.html
phpmd . xml codesize,design,naming,unusedcode --reportfile ./build/logs/pmd.xml --exclude *library/*,*tests/*,*utils/*,*build/*,*js/*,*drupal/*,*bin/*
phpcpd
https://github.com/sebastianbergmann/phpcpd
phpcpd --log-pmd ./build/logs/pmd-cpd.xml --exclude library --exclude tests --exclude utils --exclude build --exclude js --exclude drupal --exclude bin --suffixes php .
phploc
https://github.com/sebastianbergmann/phploc
phploc --log-csv ./build/logs/phploc.csv --exclude library --exclude tests --exclude utils --exclude build --exclude js --exclude drupal --exclude bin --suffixes php .
phpcs
https://github.com/squizlabs/PHP_CodeSniffer
http://pear.php.net/manual/en/package.php.php-codesniffer.php
phpcs --report=checkstyle --report-file=./build/logs/checkstyle.xml --standard=PEAR --extensions=php --ignore=*library/*,*tests/*,*utils/*,*build/*,*js/*,*drupal/*,*bin/* .
phpdoc
http://www.phpdoc.org/
phpdoc -d . -t ./build/api --ignoresymlinks --ignore *library/*,*tests/*,*utils/*,*build/*,*js/*,*drupal/*,*bin/*
phpcb
http://www.waterproof.fr/products/phpCodeBeautifier/
phpcb --log ./build/logs --source . --output ./build/code-browser --ignore=library/,tests/,utils/,build/,js/,drupal/,bin/
PhpCheckstyle
Bien que phpCheckstyle ne soit pas intégrable dans Jenkins, je donne quand même un exemple de règle d’exclusion pour ceux qui souhaiteraient l’utiliser ou l’essayer :
php run.php --src /var/www/sqa-tcm --exclude library --exclude tests --exclude utils --exclude build --exclude js --exclude drupal --exclude bin
Exemple de fichier build.xml
Il s’agit d’un exemple concret que j’ai utilisé pour intégrer un projet PHP volumineux dans Jenkins.
<project name="sqa-tcm" default="build" basedir="."> <property name="source" value="."/>
<target name="clean" description="Clean up and create artifact directories"> <delete dir="${basedir}/build/api"/> <delete dir="${basedir}/build/code-browser"/> <delete dir="${basedir}/build/coverage"/> <delete dir="${basedir}/build/logs"/> <delete dir="${basedir}/build/pdepend"/>
<mkdir dir="${basedir}/build/api"/> <mkdir dir="${basedir}/build/code-browser"/> <mkdir dir="${basedir}/build/coverage"/> <mkdir dir="${basedir}/build/logs"/> <mkdir dir="${basedir}/build/pdepend"/> </target>
<target name="phpunit" description="Run unit tests using PHPUnit and generates junit.xml and clover.xml"> <exec executable="phpunit" failonerror="false"/> </target>
<target name="parallelTasks" description="Run the pdepend, phpmd, phpcpd, phpcs, phpdoc and phploc tasks in parallel using a maximum of 2 threads."> <parallel threadCount="2"> <sequential> <antcall target="pdepend"/> <antcall target="phpmd"/> </sequential> <antcall target="phpcpd"/> <antcall target="phpcs"/> <antcall target="phpdoc"/> <antcall target="phploc"/> </parallel> </target>
<target name="pdepend" description="Generate jdepend.xml and software metrics charts using PHP_Depend"> <exec executable="pdepend"> <arg line="--jdepend-xml=${basedir}/build/logs/jdepend.xml --jdepend-chart=${basedir}/build/pdepend/dependencies.svg --overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg --ignore=library/,tests/,utils/,build/,js/,drupal/,bin/ --suffix=php ${source}" /> </exec> </target>
<target name="phpmd" description="Generate pmd.xml using PHPMD"> <exec executable="phpmd"> <arg line="${source} xml codesize,design,naming,unusedcode --reportfile ${basedir}/build/logs/pmd.xml --exclude *library/*,*tests/*,*utils/*,*build/*,*js/*,*drupal/*,*bin/*" /> </exec> </target>
<target name="phpcpd" description="Generate pmd-cpd.xml using PHPCPD"> <exec executable="phpcpd"> <arg line="--log-pmd ${basedir}/build/logs/pmd-cpd.xml --exclude library --exclude tests --exclude utils --exclude build --exclude js --exclude drupal --exclude bin --suffixes php ${source}" /> </exec> </target>
<target name="phploc" description="Generate phploc.csv"> <exec executable="phploc"> <arg line="--log-csv ${basedir}/build/logs/phploc.csv --exclude library --exclude tests --exclude utils --exclude build --exclude js --exclude drupal --exclude bin --suffixes php ${source}" /> </exec> </target>
<target name="phpcs" description="Generate checkstyle.xml using PHP_CodeSniffer"> <exec executable="phpcs" output="/dev/null"> <arg line="--report=checkstyle --report-file=${basedir}/build/logs/checkstyle.xml --standard=PEAR --ignore=*library/*,*tests/*,*utils/*,*build/*,*js/*,*drupal/*,*bin/* ${source}" /> </exec> </target>
<target name="phpdoc" description="Generate API documentation using PHPDocumentor"> <exec executable="phpdoc"> <arg line="-d ${source} -t ${basedir}/build/api --ignoresymlinks --ignore *library/*,*tests/*,*utils/*,*build/*,*js/*,*drupal/*,*bin/*" /> </exec> </target>
<target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser"> <exec executable="phpcb"> <arg line="--log ${basedir}/build/logs --source ${source} --output ${basedir}/build/code-browser ---ignore=library/,tests/,utils/,build/,js/,drupal/,bin/" /> </exec> </target>
<target name="build" depends="clean,parallelTasks,phpunit,phpcb"/> </project>
J’avais pas mal galéré en testant un par un les outils par la ligne de commande et j’espère que ce post vous aidera pour fixer correctement les paramètres de votre PIC.