Phing101 or How to staff a build orchestra

Post on 09-May-2015

3.433 views 3 download

description

Talk about PHP's buildtool Phing and how to bend it to your needs.

Transcript of Phing101 or How to staff a build orchestra

Phing 101 / How to staff a build orchestra#phpuceu11

Sonntag, 20. Februar 2011

Testing in build land

Bending Phing to your needs

Introduction

Phing in Action

Refactoring buildfiles

Best practices

Session agenda

Alternatives in PHP land

Sonntag, 20. Februar 2011

Extensible build framework

Tool that can take care of your repetitive and boring tasks

Heavily influenced by Apache Ant

External build DSL and it‘s interpreter

Cli Adapter

What‘s that Phingy thingy?

Empowers CI and CD

Enables one button push automation

Sonntag, 20. Februar 2011

Let‘s you use PHP and PHP land API‘s when extending it

Let‘s you stay in PHP land

Ships with currently ≈ 95 tasks

When to pick Phing (over Ant / Rake / ...)

No additional dependencies added (Java runtime, Ruby interpreter)

Sonntag, 20. Februar 2011

Installing the conductor

$ pear channel-discover pear.phing.info

$ pear install phing/phing

$ pear install -a phing/phing

$ phing -v

Sonntag, 20. Februar 2011

Setting the stage

<?xml version="1.0" encoding="UTF-8"?><project name="phpuceu" default="localize" basedir="."> <property name="city.name" value="Manchester" override="true"/> <target name="localize" depends="hello" description="Localize city"> <echo msg="to ${city.name}!"/> </target> <target name="hello" description="Say hello"> <echo msg="Phing says hello"/> </target></project>

Sonntag, 20. Februar 2011

Phing in Action

<?xml version="1.0" encoding="UTF-8"?><project name="phpuceu" default="localize" basedir="."> <property name="city.name" value="Manchester" override="true"/> <target name="localize" depends="hello" description="Localize city"> <echo msg="to ${city.name}!"/> </target> <target name="hello" description="Say hello"> <echo msg="Phing says hello"/> </target></project>

Sonntag, 20. Februar 2011

Phing in Action

<?xml version="1.0" encoding="UTF-8"?><project name="phpuceu" default="localize" basedir="."> <property name="city.name" value="Manchester" override="true"/> <target name="localize" depends="hello" description="Localize city"> <echo msg="to ${city.name}!"/> </target> <target name="hello" description="Say hello"> <echo msg="Phing says hello"/> </target></project>

Sonntag, 20. Februar 2011

Phing in Action

<?xml version="1.0" encoding="UTF-8"?><project name="phpuceu" default="localize" basedir="."> <property name="city.name" value="Manchester" override="true"/> <target name="localize" depends="hello" description="Localize city"> <echo msg="to ${city.name}!"/> </target> <target name="hello" description="Say hello"> <echo msg="Phing says hello"/> </target></project>

Sonntag, 20. Februar 2011

How Phing empowers CI and CD

<?phpexec('phing -f faulty-build.xml 2>&1', $output, $returnCode);var_dump($returnCode);

exec('phing -f successful-build.xml 2>&1', $output, $returnCode);var_dump($returnCode);

Sonntag, 20. Februar 2011

How Phing empowers CI and CD

Sonntag, 20. Februar 2011

Avoid repeated man tool and tool --help look ups

Identify repetitive, error-prone, and boring tasks

Why / when to automate

Have a simple Cli Adapter, to avoid the ‘bash hustle™‘

Save time, increase efficiency

Sonntag, 20. Februar 2011

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

<?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>

Wrap Cli commands with the exec task

Bending Phing to your needs

Sonntag, 20. Februar 2011

<?xml version="1.0" encoding="UTF-8"?><project name="phpuceu" default="overview" basedir="."> <target name="adhoc-setup" description="Adhoc task(s) setup"> <adhoc-task name="git-commits-by-date"><![CDATA[ class GitCommitsByDate extends Task { private $date; function setDate($date) { $this->date = $date; } function main() { $gitCommand = sprintf( 'git log --since="%s" --oneline', $this->date ); exec($gitCommand, $logs); foreach ($logs as $log) { $this->log($log); } } }]]> </adhoc-task> </target> <target name="todays-commits" depends="adhoc-setup" description="Gather todays Git commits"> <git-commits-by-date date="2011-02-20" /> </target></project>

Adhoc tasks

Bending Phing to your needs

Sonntag, 20. Februar 2011

<?phprequire_once 'phing/Task.php';require_once 'phing/BuildException.php';class GitTagTask extends Task { private $annotation;

public function setAnnotation($annotation) { $this->annotation = $annotation; } public function init() {} public function main() { $this->_validateProperties(); $gitCommand = sprintf("git tag -a %s -m ''", $this->annotation); $this->log('Executing "%s"', $gitCommand); exec($gitCommand); $project = $this->getProject(); $logMsg = sprintf('Tagged %s with %s', $project->getName(), $this->annotation); $this->log($logMsg); } /** * @throws Phing/BuildException */ private function _validateProperties() {}}

Custom tasks

Bending Phing to your needs

Sonntag, 20. Februar 2011

<?xml version="1.0" encoding="UTF-8"?><project name="phpuceu" default="build" basedir="."> <taskdef name="git-tag" classname="phing-tasks.GitTagTask" /> <property name="tagable.build" value="false" override="true" /> <property name="tag.annotation" value="" override="true" />

<target name="-tag-project"> <if> <equals arg1="${tagable.build}" arg2="true" /> <then> <git-tag annotation="${tag.annotation}"/> </then> <else> <echo message="Skipping project tagging" /> </else> </if> </target> <target name="build" depends="inspect,test,-tag-project"> <echo msg="Building..." /> </target>

<target name="inspect"> <echo msg="Inspecting..." /> </target> <target name="test"> <echo msg="Testing..." /> </target></project>

Using custom tasks

Bending Phing to your needs

Sonntag, 20. Februar 2011

Connect with Michiel Rook to get Svn commit privileges

Transforming custom tasks into official ones

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.phpDocument task & commit

Sonntag, 20. Februar 2011

Make it known to the Phing Cli at runtime with the -logger option

Implement Phing‘s BuildListener interface

To use it permanently add it to the Phing bash script

Crafting custom build listeners

Sonntag, 20. Februar 2011

Crafting custom build listeners

<?phpclass 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

Using custom build listeners

$ phing -logger phing.listener.BuildhawkLogger

$ buildhawk --title 'Examplr' > examplr-build-times.html

Sonntag, 20. Februar 2011

Unit tests for custom tasks

Testing tasks and buildfiles

Structure and execution tests for the buildfile / build itself

Sonntag, 20. Februar 2011

Integration, formal tests for the buildfile itself

Testing tasks and buildfiles

class GitHubIssuesTaskTest extends PHPUnit_Framework_TestCase { private $task; private $testReportDirectory; public function setUp() { $this->task = new GitHubIssuesTask; $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

Integration, formal tests for the buildfile itself

Testing tasks and buildfiles

class GitHubIssuesTaskTest extends PHPUnit_Framework_TestCase{ private $task; private $testReportDirectory; public function setUp() { // 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

Structure and execution tests for the buildfile / build itself

Testing tasks and buildfiles

/** * @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

Structure and execution tests for the buildfile / build itself

Testing tasks and buildfiles

/** * @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

Structure and execution tests for the buildfile / build itself

Testing tasks and buildfiles

/** * @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

Extract target

Refactoring buildfiles

<target name="test-mongodb-daos" description="Testing MongoDb based Daos"> <exec command="mongoimport --db ${mongo.test.db} --collection ${mongo.test.data.collection} --file ${mongo.test.data.file}" logoutput="true" /> <exec command="phpunit --configuration mongodb-test.xml" dir="${test.directory}" logoutput="true" /></target>

<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>

<target name="test-mongodb-daos" depends="setup-mongodb-test-data" description="Testing MongoDb based Daos"> <exec command="phpunit --configuration mongodb-test.xml" dir="${test.directory}" logoutput="true" /></target>Sonntag, 20. Februar 2011

Extract target

Introduce property file

Refactoring buildfiles

<property name="mongo.test.db" value="orchestra" override="true" /><property name="mongo.test.data.file" value="${project.basedir}/fixtures/orchestra.json" /><property name="mongo.test.data.collection" value="testcollection" />

<property file="mongodb-test.properties" />

[mongodb-test.properties]mongo.test.db=orchestramongo.test.data.file=fixtures/orchestra.jsonmongo.test.data.collection

+

Sonntag, 20. Februar 2011

Replace comment with description

Extract target

Introduce property file

Refactoring buildfiles

<!-- Target runs the code base against PHP_CodeSniffer ruleset --><target name="inspect" depends="lint"> <phpcodesniffer ... /></target>

<target name="inspect" depends="lint" description="Sniff code base against PHP_CodeSniffer ruleset"> <phpcodesniffer ... /></target>

Sonntag, 20. Februar 2011

Enforce internal target

Replace comment with description

Extract target

Introduce property file

Refactoring buildfiles

<target name="deploy" depends="test-setup, test, smoke-test" description="Deploys the project into ..."> <!-- omitted --></target>

<target name="-deploy" depends="test-setup, test, smoke-test" description="Deploys the project into ..."> <!-- omitted --></target>

Sonntag, 20. Februar 2011

Enforce internal target

Replace comment with description

Extract target

Introduce property file

Introduce distinct target naming

Refactoring buildfiles

<property name="deployment-mode" value="staging" override="true" /><target name="cloneGitRepos"> <!-- ... --></target>

<property name="deployment.mode" value="staging" override="true" /><target name="clone-git-repos"> <!-- ... --></target>

Sonntag, 20. Februar 2011

Threat buildfiles as ‘first-class‘ code citizens

Describe your targets

Follow an iterative approach

Apply the Martin Fowler‘s / Don Roberts “Rule of Three“

Best practices

Establish a common buildfile coding standard

Sonntag, 20. Februar 2011

phake

taskman

pake

Alternatives in PHP land

<?phpdesc("Say hello");task('hello', function() { echo 'Phake says hello '; });

desc("Localize city");task('localize','hello', function($app) { $cityName = 'Manchester'; if (isset($app['city.name']) && $app['city.name'] !== '') { $cityName = $app['city.name']; } echo 'to ' . $cityName . '!' . PHP_EOL; });

task('default', 'localize');

Sonntag, 20. Februar 2011

phake

taskman

pake

Alternatives in PHP land

<?phpdesc("Say hello");task('hello', function() { echo 'Phake says hello '; });

desc("Localize city");task('localize','hello', function($app) { $cityName = 'Manchester'; if (isset($app['city.name']) && $app['city.name'] !== '') { $cityName = $app['city.name']; } echo 'to ' . $cityName . '!' . PHP_EOL; });

task('default', 'localize');

Sonntag, 20. Februar 2011

phake

taskman

pake

Alternatives in PHP land

<?phpdesc("Say hello");task('hello', function() { echo 'Phake says hello '; });

desc("Localize city");task('localize','hello', function($app) { $cityName = 'Manchester'; if (isset($app['city.name']) && $app['city.name'] !== '') { $cityName = $app['city.name']; } echo 'to ' . $cityName . '!' . PHP_EOL; });

task('default', 'localize');

Sonntag, 20. Februar 2011

@raphaelstolt

Brought to you by

Sonntag, 20. Februar 2011

[5] http://www.flickr.com/photos/rodrigodavid/4074212668

[9] http://www.flickr.com/photos/roland/1924450950

[8] http://www.flickr.com/photos/crazytales562/3013076129

[7] http://www.flickr.com/photos/52569650@N00/4310444273

[6] http://www.flickr.com/photos/melitron/4923829202

[4] http://www.flickr.com/photos/clarazamith/3816864822

[3] http://www.flickr.com/photos/rotterdamsphilharmonischorkest/

[2] http://www.flickr.com/photos/shoppingdiva/3695515673

[1] http://www.flickr.com/photos/seosmh/1352680740

Image credits

Sonntag, 20. Februar 2011