1. Phing 101 / How to staff a build orchestra #phpuceu11
Sonntag, 20. Februar 2011
2. Session agenda
Introduction
Phing in Action
Bending Phing to your needs
Testing in build land
Refactoring buildfiles
Best practices
Alternatives in PHP land
Sonntag, 20. Februar 2011
3. What‘s that Phingy thingy?
External build DSL and it‘s interpreter
Extensible build framework
Heavily influenced by Apache Ant
Enables one button push automation
Empowers CI and CD
Tool that can take care of your repetitive
and boring tasks
Cli Adapter
Sonntag, 20. Februar 2011
4. When to pick Phing (over Ant / Rake / ...)
Let‘s you stay in PHP land
Let‘s you use PHP and PHP land API‘s
when extending it
No additional dependencies added (Java
runtime, Ruby interpreter)
Ships with currently ≈ 95 tasks
Sonntag, 20. Februar 2011
5. Installing the conductor
$ pear channel-discover pear.phing.info
$ pear install phing/phing
$ pear install -a phing/phing
$ phing -v
Sonntag, 20. Februar 2011
12. Why / when to automate
Identify repetitive, error-prone, and
boring tasks
Avoid repeated man tool and
tool --help look ups
Have a simple Cli Adapter, to
avoid the ‘bash hustle™‘
Save time, increase efficiency
Sonntag, 20. Februar 2011
13. Picking your orchestra staff
Get to know the official Phing
guide
Pick the tasks suitable for your
identified problem domain
Bundle them in callable targets
Define the orchestration flow
Sonntag, 20. Februar 2011
14. Bending Phing to your needs
Wrap Cli commands with the
exec task
<?xml version="1.0" encoding="UTF-8"?>
<project name="phpuceu" default="setup-mongodb-test-data"
basedir=".">
<property name="mongo.test.db" value="orchestra-test"
override="true" />
<property name="mongo.test.data.file"
value="${project.basedir}/fixtures/orchestra.json" />
<property name="mongo.test.data.collection"
value="testcollection" />
<target name="setup-mongodb-test-data"
description="Setting up MongoDb test data">
<exec command="mongoimport --db ${mongo.test.db} --collection
${mongo.test.data.collection} --file ${mongo.test.data.file}"
logoutput="true" />
</target>
</project>
Sonntag, 20. Februar 2011
18. Transforming custom tasks
into official ones
Connect with Michiel Rook to get
Svn commit privileges
Add task and entourage to
$PHING_TRUNK/task/ext/*
Add task to $PHING_TRUNK/
tasks/defaults.properties
Add PEAR packages depen-
dencies to $PHING_TRUNK/build/
BuildPhingPEARPackageTask.php
Document task & commit
Sonntag, 20. Februar 2011
19. Crafting custom build listeners
Implement Phing‘s BuildListener
interface
Make it known to the Phing Cli at
runtime with the -logger option
To use it permanently add it to the
Phing bash script
Sonntag, 20. Februar 2011
20. Crafting custom build listeners
<?php
class BuildhawkLogger extends DefaultLogger {
private $_gitNotesCommandResponse = null;
public function buildFinished(BuildEvent $event) {
parent::buildFinished($event);
if ($this->_isProjectGitDriven($event)) {
$error = $event->getException();
if ($error === null) {
$buildtimeForBuildhawk = $this->_formatBuildhawkTime(
Phing::currentTimeMillis() - $this->startTime);
if (!$this->_addBuildTimeAsGitNote($buildtimeForBuildhawk)) {
$message = sprintf("Failed to add git note due to '%s'",
$this->_gitNotesCommandResponse);
$this->printMessage($message, $this->err, Project::MSG_ERR);
}
}
}
}
private function _isProjectGitDriven(BuildEvent $event) {
$project = $event->getProject();
$projectGitDir = sprintf('%s/.git', $project->getBasedir()->getPath());
return file_exists($projectGitDir) && is_dir($projectGitDir);
}
private function _formatBuildhawkTime($micros) {
return sprintf("%0.3f", $micros);
}
private function _addBuildTimeAsGitNote($buildTime) {
$gitCommand = sprintf("git notes --ref=buildtime add -f -m '%s' HEAD 2>&1",
$buildTime);
$gitNotesCommandResponse = exec($gitCommand, $output, $return);
if ($return !== 0) {
$this->_gitNotesCommandResponse = $gitNotesCommandResponse;
return false;
}
return true;
Sonntag, 20. }
Februar 2011
21. Using custom build listeners
$ phing -logger phing.listener.BuildhawkLogger
$ buildhawk --title 'Examplr' > examplr-build-times.html
Sonntag, 20. Februar 2011
22. Testing tasks and buildfiles
Unit tests for custom tasks
Structure and execution tests for
the buildfile / build itself
Sonntag, 20. Februar 2011
23. Testing tasks and buildfiles
class GitHubIssuesTaskTest extends PHPUnit_Framework_TestCase {
private $task;
private $testReportDirectory;
Integration, formal tests for the
public function setUp() {
$this->task = new GitHubIssuesTask;
buildfile itself
$this->task->init();
$this->testReportDirectory = TEST_DIR
. DIRECTORY_SEPARATOR . 'test-ghi-dir';
}
/**
* @test
* @expectedException BuildException
* @dataProvider nonAcceptedReportTypesProvider
*/
public function taskShouldThrowExceptionOnNonAcceptedReportTypes($type) {
$this->task->setReportType($type);
$this->task->main();
}
* @return array
*/
public function nonAcceptedReportTypesProvider()
{
return array(
array('doc'),
array('vid'),
array('pdf'),
);
}
}
Sonntag, 20. Februar 2011
24. Testing tasks and buildfiles
class GitHubIssuesTaskTest extends PHPUnit_Framework_TestCase
{
private $task;
private $testReportDirectory;
Integration, formal tests for the
public function setUp()
{
buildfile itself
}
// omitted
/**
* @test
*/
public function taskShouldCreateReportDirectoryWhenNotExisting()
{
Phing::setProperty('host.fstype', 'UNIX');
$this->assertFalse(file_exists($this->testReportDirectory));
$this->task->setReportType('txt');
$this->task->setRepository('name:repos');
$this->task->setReportDirectory($this->testReportDirectory);
$project = $this->getMock('Projekt', array('logObject'));
$project->expects($this->once())
->method('logObject')
->with($this->anything());
$this->task->setProject($project);
$this->task->main();
$this->assertTrue(file_exists($this->testReportDirectory));
$this->assertTrue(is_dir($this->testReportDirectory));
$this->assertTrue(rmdir($this->testReportDirectory));
}
}
Sonntag, 20. Februar 2011
25. Testing tasks and buildfiles
Structure and execution tests for
the buildfile / build itself
/**
* @test
* @group structure
*/
public function buildfileShouldContainACleanTarget() {
$xml = new SimpleXMLElement($this->_buildfileXml);
$cleanElement = $xml->xpath("//target[@name='clean']");
$this->assertTrue(count($cleanElement) === 0,
"Buildfile doesn't contain a clean target");
}
Sonntag, 20. Februar 2011
26. Testing tasks and buildfiles
Structure and execution tests for
the buildfile / build itself
/**
* @test
* @group structure
*/
public function targetBuildShouldDependOnCleanTarget() {
$xml = new SimpleXMLElement($this->_buildfileXml);
$xpath = "//target[@name='build']/@depends";
$dependElement = $xml->xpath($xpath);
$this->assertTrue(count($dependElement) > 0,
'Target build contains no depends attribute'
);
$dependantTasks = array_filter(explode(' ',
trim($dependElement[0]->depends))
);
$this->assertContains('clean', $dependantTasks,
"Target build doesn't depend on the clean target"
);
}
Sonntag, 20. Februar 2011
27. Testing tasks and buildfiles
Structure and execution tests for
the buildfile / build itself
/**
* @test
* @group execution
*/
public function initTargetShouldCreateInitialBuildArtifacts() {
$this->_isTearDownNecessary = true;
$this->_buildfileRunner->runTarget(array('init'));
$expectedInitArtifacts = array(
"{$this->_buildfileBasedir}/build",
"{$this->_buildfileBasedir}/build/logs/performance/",
"{$this->_buildfileBasedir}/build/doc",
"{$this->_buildfileBasedir}/build/reports"
);
foreach ($expectedInitArtifacts as $artifact) {
$this->assertFileExists(
$artifact,
"Expected file '{$artifact}' doesn't exist"
);
}
}
Sonntag, 20. Februar 2011
33. Best practices
Follow an iterative approach
Apply the Martin Fowler‘s / Don
Roberts “Rule of Three“
Describe your targets
Threat buildfiles as ‘first-class‘
code citizens
Establish a common buildfile
coding standard
Sonntag, 20. Februar 2011