- Тестирование;
- Проведение необходимых изменений в БД;
- Анализ исходного кода;
- Генерация документации;
- Минимизация;
- Какие-то другие специфические для проекта задачи, которые можно выполнять автоматизированно.
Для PHP-проекта фактическим стандартом для задач build'а является приложение Phing, очень похожее своим подходом на Ant для Java. Подробно изучить Phing можно с помощью документации на официальном сайте, а также будет полезна вот эта развернутая статья (кстати не только про Phing). В этом же посте я расскажу, что Phing довольно стабильно развивается с релизом примерно раз в три месяца, в заключении приведу пример build-скрипта.
Итак начнем с установки (возможностей у Phing'а много, для некоторых типов задач понадобится установить дополнительные библиотеки):
pear channel-discover pear.phing.info
pear install phing/phingТеперь создадим простой build-скрипт build.xml в корне проекта.
<?xml version="1.0" encoding="UTF-8"?> <project name="Phing example" default="default"> <property name="build.dir" value="." /> <!-- ============================================ --> <!-- (DEFAULT) Target: default --> <!-- ============================================ --> <target name="default" depends="run.speech.engine"> <echo msg="Hello world!" /> </target> <!-- ============================================ --> <!-- Target: run.speech.engine --> <!-- ============================================ --> <target name="run.speech.engine"> <echo msg="I can speak now!" /> </target> </project>Выполнив команду
phing (можно также запускать phing -f build.xml для указания имени запускаемого скрипта) мы получим такой вывод:
Buildfile: /home/username/build-example/build.xml
Phing example > run.speech.engine:
[echo] I can speak now!
Phing example > default:
[echo] Hello world!
BUILD FINISHED
Total time: 0.1666 seconds
Одной из интересных практик для написания build-скриптов является создание несколько разных файлов для разных сред. Например: build-dev.xml, build-stage.xml, build-production.xml. Это действительно имеет смысл, т.к. задачи для каждой среды исполнения разные. Дальше мы попробуем написать два скрипта: build-dev.xml и build-production.xml.
Первый скрипт для среды разработки будет решать задачи проверки кода, запуска тестов и генерации документации. Начнем с проверки кода на синтаксические ошибки, для этого нужно использовать тип задач PhpLintTask:
<?xml version="1.0" encoding="UTF-8"?>
<project name="Phing example" default="default">
<property name="build.dir" value="." />
<!-- ============================================ -->
<!-- (DEFAULT) Target: default -->
<!-- ============================================ -->
<target name="default" depends="lint.legacy">
<echo msg="All done." />
</target>
<!-- ============================================ -->
<!-- Target: lint.legacy -->
<!-- ============================================ -->
<target name="lint.legacy">
<phplint>
<fileset dir="${build.dir}/application/lib">
<include name="**/*.php" />
</fileset>
</phplint>
</target>
</project>
После запуска вывод будет примерно таким:
Buildfile: /home/username/build-example/build-dev.xml
Phing example > lint.legacy:
[phplint] ./application/lib/auth.class.php: No syntax errors detected
[phplint] ./application/lib/session.class.php: No syntax errors detected
[phplint] ./application/lib/video.class.php: No syntax errors detected
Phing example > default:
[echo] All done.
BUILD FINISHED
Total time: 2.3877 seconds
Переходим к запуску тестов, и здесь можно использовать задачи PHPUnitTask или SimpleTestTask в зависимости от используемого фреймворка для тестирования.
<?xml version="1.0" encoding="UTF-8"?>
<project name="Phing example" default="default">
<property name="build.dir" value="." />
<!-- ============================================ -->
<!-- (DEFAULT) Target: default -->
<!-- ============================================ -->
<target name="default" depends="lint.legacy, run.tests">
<echo msg="All done." />
</target>
<!-- ============================================ -->
<!-- Target: lint.legacy -->
<!-- ============================================ -->
<target name="lint.legacy">
<phplint>
<fileset dir="${build.dir}/application/lib">
<include name="**/*.php" />
</fileset>
</phplint>
</target>
<!-- ============================================ -->
<!-- Target: run.tests -->
<!-- ============================================ -->
<target name="run.tests">
<phpunit haltonfailure="true" haltonerror="true">
<formatter type="plain" usefile="false" />
<batchtest>
<fileset dir="${build.dir}/application/tests">
<include name="*Test.php" />
</fileset>
</batchtest>
</phpunit>
</target>
</project>
Последнее, что мы решили делать, это получать документацию API исходного кода. Для этого я рекомендую использовать задачу DocBloxTask.
<?xml version="1.0" encoding="UTF-8"?>
<project name="Phing example" default="default">
<property name="build.dir" value="." />
<!-- ============================================ -->
<!-- (DEFAULT) Target: default -->
<!-- ============================================ -->
<target name="default" depends="lint.legacy, run.tests, api.docs">
<echo msg="All done." />
</target>
<!-- ============================================ -->
<!-- Target: lint.legacy -->
<!-- ============================================ -->
<target name="lint.legacy">
<phplint>
<fileset dir="${build.dir}/application/lib">
<include name="**/*.php" />
</fileset>
</phplint>
</target>
<!-- ============================================ -->
<!-- Target: run.tests -->
<!-- ============================================ -->
<target name="run.tests">
<phpunit haltonfailure="true" haltonerror="true">
<formatter type="plain" usefile="false" />
<batchtest>
<fileset dir="${build.dir}/application/tests">
<include name="*Test.php" />
</fileset>
</batchtest>
</phpunit>
</target>
<!-- ============================================ -->
<!-- Target: api.docs -->
<!-- ============================================ -->
<target name="api.docs">
<docblox title="API Docs" destdir="${build.dir}/public/apidocs"
quiet="true">
<fileset dir="${build.dir}/application/lib">
<include name="**/*.php" />
</fileset>
</docblox>
</target>
</project>
В итоге при запуске phing -f build-dev.xml у нас получается такой вывод:
Buildfile: /home/username/build-example/build-dev.xml
Phing example > lint.legacy:
...
Phing example > run.tests.legacy:
...
Phing example > api.docs:
...
Phing example > default:
[echo] All done.
BUILD FINISHED
Total time: 26.5158 seconds
Переходим к build-production.xml. Здесь задачи уже другие, нам более интересно минимизировать некоторые файлы (css, js) и объединить небольшие файлы одного типа. У нас получится примерно такой build-скрипт:
<?xml version="1.0" encoding="UTF-8"?>
<project name="Phing example" default="default">
<property name="build.dir" value="./public" />
<!-- ============================================ -->
<!-- (DEFAULT) Target: default -->
<!-- ============================================ -->
<target name="default"
depends="minify.css.all, minify.js.all, concatenate.css, concatenate.js">
<echo msg="All done." />
</target>
<!-- ============================================ -->
<!-- Target: minify.css.all -->
<!-- ============================================ -->
<target name="minify.css.all">
<echo msg="Compressing CSS with YUI Compressor" />
<fileset dir="${build.dir}/css" id="fileset.css">
<include name="global.css" />
<include name="blocks.css" />
</fileset>
<foreach param="filename" absparam="absfilename" target="minify">
<fileset refid="fileset.css" />
</foreach>
<move todir="${build.dir}/css" overwrite="true">
<mapper type="glob" from="*.css" to="*.min.css" />
<fileset refid="fileset.css" />
</move>
</target>
<!-- ============================================ -->
<!-- Target: minify.js.all -->
<!-- ============================================ -->
<target name="minify.js.all">
<echo msg="Compressing JS with YUI Compressor" />
<fileset dir="${build.dir}/js" id="fileset.js">
<include name="global.js" />
</fileset>
<foreach param="filename" absparam="absfilename" target="minify">
<fileset refid="fileset.js" />
</foreach>
<move todir="${build.dir}/js" overwrite="true">
<mapper type="glob" from="*.js" to="*.min.js" />
<fileset refid="fileset.js" />
</move>
</target>
<!-- ============================================ -->
<!-- Target: minify -->
<!-- ============================================ -->
<target name="minify">
<echo msg="Minimizing: ${filename}" />
<exec command="java -jar yuicompressor.jar ${absfilename} -o ${absfilename}" />
</target>
<!-- ============================================ -->
<!-- Target: concatenate.css -->
<!-- ============================================ -->
<target name="concatenate.css" depends="minify.css.all">
<echo msg="Concatenating CSS" />
<delete file="${build.dir}/css/all.min.css" />
<append destFile="${build.dir}/css/all.min.css">
<fileset dir="${build.dir}/css">
<include name="global.min.css" />
<include name="blocks.min.css" />
</fileset>
</append>
</target>
<!-- ============================================ -->
<!-- Target: concatenate.js -->
<!-- ============================================ -->
<target name="concatenate.js" depends="minify.js.all">
<echo msg="Concatenating JS" />
<delete file="${build.dir}/js/all.min.js" />
<append destFile="${build.dir}/js/all.min.js">
<fileset dir="${build.dir}/js">
<include name="global.min.js" />
<include name="gallery.min.js" />
</fileset>
</append>
</target>
</project>
Здесь для минимизации мы использовать YUI compressor, а для объединения файлов — директиву append. Вывод будет примерно таким:
Buildfile: /home/username/build-example/build-dev.xml
Phing example > minify.css.all:
[echo] Compressing CSS with YUI Compressor
Phing example > minify.js.all:
[echo] Compressing JS with YUI Compressor
Phing example > concatenate.css:
[echo] Concatenating CSS
...
Phing example > concatenate.js:
[echo] Concatenating JS
...
Phing example > default:
[echo] All done.
BUILD FINISHED
Total time: 3.3199 seconds
На этом пример build для PHP проекта можно считать завершенным. Спасибо за внимание, и делитесь, для чего вы используете build в ваших проектах!
2 коммент.:
В предыдущем каменте есть некоторая несогласованность - следствие творческих мук при написании камента и невозможности отредактировать сообщение :)
Миша, так а предыдущий коммент ты удалил? Не против, если я его восстановлю :)
"Дефолтную цель использую для инициализации проекта после чекаута из SVN - настройка прав на каталоги, инициализация разработческого конфига и т.д.
Так же есть цель для сборки/выкладки документации (вызов doxygen).
Ну и основная цель - сборка пакета. Включает себя проверку отсутствия не закоммиченных изменений, создание тага, чекаут этого тага, инициализация боевого конфига, сборка deb-пакета, выкладка в репозитарий или установка на сервер CI.
Phing не умеет (не умел) полноценно работать с командами типа svn, scp, fakeroot, test, ssh, dpkg, sudo. Поэтому большая часть моих целей сотоит
Писать для всего этого расширения Phing было как0то влом, поэтому всё сводиться к банальному вызову exec. Дёшево, но сердито.
Для всего этого, конечно можно было использовать ant, bash или что-нибудь ещё. Но phing всё же как-то ближе к PHP :)"
Отправить комментарий