Выкат проектов с помощью Apache ANT

Шалом тебе, дражайший читатель.

В этом посте речь пойдёт о вещах сугубо технических, и мало кому интересных. Однако, довольно насущных в сфере разработки всяческого программного обеспечения. Имеется ввиду автоматизированный «выкат» (ну, или «сборка») какого-либо проекта, путём написания для этого скриптов развёртки. Всё ещё хотите читать?))  Ну ладно, ладно.

Так вот, о чем это я. Систем выката, как известно, существует великое множество — например самой распространённой в *nix мире является make — по крайней мере если не брать красноглазиков из мира gentoo. Как вариант некоторые рассматривают cmake. Возможно довольно неплохие вещи, но скажем перенос их на другую платформу как правило означает переписывание всех скриптов заново. Да и синтаксис их написания практически один в один напоминает unix-shell, который я лично стараюсь использовать по минимуму. Ужасен он, ИМХО, что бы по этому не думали труЪ-адепты секты линуксоидов, хотя надо признать, что он предоставляет широкие возможности.

Так вот, есть более-менее нормальная такая альтернатива. Называется она Apache Ant, и в принципе с задачей она справиться вполне способна. Единственный её минус — очень мало информации на русском языке. Но для начала, давайте подумаем — зачем вообще может понадобиться делать лишнюю работу, и писать ещё какие-то билд-скрипты, ант устанавливать, и вообще, фигнёй страдать?

В принципе, если у вас небольшой проект, и весь процесс его развёртки, это просто svn checkout, то заморачиваться не стоит. Зачем вам лишние проблемы? А вот если у вас проект хранится в нескольких репозиториях (распространённый вариант — фреймворк в одном месте, а проекты его использующие — в другом, и разработка их ведётся параллельно), либо есть несколько конфигураций готового развёрнутого проекта (Dev-версия, или версия для разработки, Test — версия для тестирования в условиях, максимально приближенных к боевым, Production — версия для боевой работы), то вам уже надо как-то их упорядочить — на уровне развёртки. Видели ли вы конфиги, от которых зависит поведение веб-решений, которые занимают хотя бы 15-20 мегабайт кода (проекты, не конфиги, разумеется, от конфига на 20 мегабайт я бы лично кирпичную будку выложил)? Жуткое зрелище, поверьте. В этом случае вам очень пригодится система конфигурирования во время разворачивания, которая будет генерировать, скажем, файлы с пресетами из шаблонов, выкатывать разные компоненты из репозиториев, вызывать всяческие программы и выполнять команды, что в итоге должно дать работающий продукт.

Так вот, возвращаясь к ANT’у (кстати, ant в переводе с английского — муравей). Чем приглянулось это решение? Во-первых, синтаксис билд скриптов хотя и ограничен в некоторых аспектах, тем не менее он куда как проще и понятнее, чем гиковский shell, подобие которого используется в make. Во-вторых, всё-таки шелл-команды из него вызывать вполне можно. По сути, билд-скрипты для ant — это xml-файлы, в котором описана последовательность команд, которые требуется выполнить, чтобы получить рабочее решение. В третьих, для ANT Существует довольно много модулей, расширяющих его функциональность. В четвёртых — ант имеет встроенную поддежку svn. В пятых — он кросс-платформенный, т.е. одинаково хорошо будет работать и на Windows и на Linux машинах (заметьте, я нигде не сказал, что билды будут одинаково работать, только о самом ant’е речь пока). Ах да, и в шестых — идейные ненавистники такой технологии как Java, смело идут на хуй, так как ANT написан на Java. Surprise, suckers! :)

В нашей работе мы используем именно ANT, и, как уже упоминалось, очень сложно найти по нему информацию на русском языке. В некоторых проектов есть разделение билд-скриптов по уровням: «билды верхнего уровня, билды проектного уровня». Что это значит? Это разделение труда — билд верхнего уровня выкатывает из репозиториев требуемые файлы (движок, проекты), а затем передаёт управление соответственно билдам проекта или билдам движка (фреймворка), которые отрабатывают уже свою задачу — конфигурируют, скажем, движок, в соответствии с заложенным в них алгоритмом, но при этом используя часть конфигурации «головных» билдов. В данной статье подобные схемы описаны не будут, потому как объём получится совсем невообразимый, но в целом додумать не сложно — главное знать, что такие возможности присутствуют.

Так вот, для начала — ну, чтобы попробовать то, что тут описано — надо иметь сам ANT установленный. Скачать его можно на официальном сайте. Там же, в принципе, описано и то, как его установить/настроить (просто он поставляется в виде zip-архива, и никакого специфичного инсталлятора не имеет). В Linux|UNIX-like системах я думаю можно взять его из репозитория, это будет проще.

Итак, сердце «билдов» (билд-скриптов) это файл build.xml, который содержит начальные инструкции по сборке. Можно, конечно, всё хранить в нём, но это будет ужасно — всё в куче, в одном месте… В общем — нехорошо. Намного лучше описать в build.xml алгоритм выбора конфигурации, а сами конфигурационные файлы — хранить по отдельности. Скажем, у нас будет отдельная папка build, в которой хранится всё, что требуется для разворачивания проекта. И предположим, она будет иметь следующую структуру:

/build
    /properties
    /scripts
    /templates
    build.xml

Здесь в папке properties хранятся конфигурационные файлы (для простоты всего две конфигурации — Develop & Production, плюс общий для всех файл Common в котором хранятся настройки единые для всех конфигураций). Соответственно, для того, чтобы построить проект, необходимо будет войти в директорию build, чтобы она стала текущей, и в ней выполнить команду например:

ant -Dbt=D install

Дальше ант должен подцепить файл build.xml, и начать выполнять инструкции в нём описанные. Собственно, вот и сам build:

<?xml version="1.0"?>
<!-- Обратите внимание на параметр basedir - в дальнейшем он активно используется -->
<project name="Some Project" basedir="." default="install">
	<!-- BEGIN:Load of properties... -->
	<echo>Loading properties of build...</echo>

	<!-- Здесь мы тащемта выбираем, какую конфигурацию мы будем разворачивать. Develop or Prod -->

	<!-- Property ${bt} MUST BE set in command line -->
	<property name="bt" value="!" />
	<condition property="build.type" value="Develop">
		<or>
			<equals arg1="d" arg2="${bt}" casesensitive="false" trim="true" />
			<equals arg1="dev" arg2="${bt}" casesensitive="false" trim="true" />
			<equals arg1="develop" arg2="${bt}" casesensitive="false" trim="true" />
		</or>
	</condition>

	<condition property="build.type" value="Production">
		<or>
			<equals arg1="p" arg2="${bt}" casesensitive="false" trim="true" />
			<equals arg1="prod" arg2="${bt}" casesensitive="false" trim="true" />
			<equals arg1="production" arg2="${bt}" casesensitive="false" trim="true" />
		</or>
	</condition>

	<property name="build.type" value="${bt}" />

	<!-- Сия конструкция подгружает файл с пресетами, в зависимости от выбранной конфигурации сборки -->
	<property name="build.properties.filename" value="${basedir}/properties/${build.type}" />

	<condition property="incorrect.build.type">
		<not>
			<available file="${build.properties.filename}" />
		</not>
	</condition>

	<!-- А нет у нас такого конфига! -->
	<fail if="incorrect.build.type" message="Incorrect build type [${build.type}] was received - build stopped!" />

	<property file="${build.properties.filename}" />
	<!-- И что бы ни случилось - подгружаем настройки Common, общие для всех конфигураций -->
	<property file="${basedir}/properties/Common" />
	<!-- END:Load of properties -->

	<!-- А дальше конфигурируем SVN -->

	<!-- BEGIN:SVN Configure -->
	<echo>Configuring subversion...</echo>

	<!-- SETUP of SVN-Action type -->
	<property name="sa" value="checkout" />
	<property name="svn.action" value="${sa}" />
	<condition property="svn.checkout" value="true">
		<or>
			<equals arg1="co" arg2="${svn.action}" casesensitive="false" trim="true" />
			<equals arg1="checkout" arg2="${svn.action}" casesensitive="false" trim="true" />
		</or>
	</condition>
	<condition property="svn.update" value="true">
		<or>
			<equals arg1="up" arg2="${svn.action}" casesensitive="false" trim="true" />
			<equals arg1="update" arg2="${svn.action}" casesensitive="false" trim="true" />
		</or>
	</condition>

	<property name="project.svn.password" value="${sp}" />

	<!-- SvnAnt Setup -->
	<typedef resource="org/tigris/subversion/svnant/svnantlib.xml" classpath="svnant.jar" />
	<!-- END:SVN Configure -->

	<!-- А вот тут мы как раз в зависимости от заданного "таргета" выбираем, что делать будем - устанавливать,
		линковать, или просто переконфигурировать. Разница в следующем: в install мы просто вытягиваем все требуемые
		файлы из репозиториев, и размещаем их так, как нам нужно, с учётом всяческих иерархий и прочего.

		В link мы можем много чего. Например самое простое - в UNIX системах понапроставлять симлинков на нужные
		директории в проекте, или там вызвать какой-нибудь внешний линковщик, или ещё что-нибудь... В общем логически
		данный уровень выката проекта - наиболее абстрактен, вы можете в нём по идее творить что угодно.

		Ну и наконец configure в данном случае просто генерирует конфигурационные файлы - для развёрнутого продукта.

		Важно помнить, что вы можете задавать любые цели, и как угодно разделять логику сборки - что-то у вас может
		быть, чего-то не быть - в общем, всё на ваш вкус.
	-->

	<!-- BEGIN: TARGETS -->
	<target name="install">
		<ant antfile="${basedir}/scripts/project_install.xml" />
	</target>

	<target name="configure">
		<ant antfile="${basedir}/scripts/project_configure.xml" />
	</target>

	<target name="link">
		<ant antfile="${basedir}/scripts/project_link.xml" />
		<ant antfile="${basedir}/scripts/project_configure.xml" />
	</target>

<!-- А сюда, собственно, мы помещаем информацию о том, что мы вообще выкатили. В дальнейшем эта информация помогает
	тому же анту ориентироваться в версиях выкаченного, да и самим вполне можно будет посмотреть, что там происходило
-->
<!-- BEGIN: DO NOT place any spaces at the beginning of lines in that target -->
<target name="save.branchname">
<echo file="installed_branches_project.txt" append="true" level="info">${forum.svn.project.branch}
</echo>
</target>
<!-- END. -->

<!-- END: TARGETS -->

</project>

Как пример — project_configure.xml для одного форума:

<?xml version="1.0"?>
<project name="Some Project" default="configure">
	<target name="configure">
		<echo> Configure: START </echo>
		<!-- Forum config -->

		<delete file="${forum.install.path}/conf_global.php" quiet="true" />
		<copy file="${basedir}/templates/conf_global.php.tpl" tofile="${forum.install.path}/conf_global.php" />

		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.driver#" value="${forum.sql.driver}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.host#" value="${forum.sql.host}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.port#" value="${forum.sql.port}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.database#" value="${forum.sql.database}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.user#" value="${forum.sql.user}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.pass#" value="${forum.sql.pass}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.tbl_prefix#" value="${forum.sql.tbl_prefix}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.sql.debug#" value="${forum.sql.debug}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.host#" value="${forum.host}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.settings.group.banned#" value="${forum.settings.group.banned}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.settings.group.admin#" value="${forum.settings.group.admin}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.settings.group.guest#" value="${forum.settings.group.guest}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.settings.group.member#" value="${forum.settings.group.member}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.settings.group.auth#" value="${forum.settings.group.auth}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.locale#" value="${forum.locale}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.date_start#" value="${forum.date_start}" />
		<replace file="${forum.install.path}/conf_global.php" token="#ANT:forum.hooks.disabled#" value="${forum.hooks.disabled}" />

		<!-- / Forum config -->

		<echo> Configure: END </echo>
	</target>
</project>

Ах да, чуть не забыл. Ещё требуется конфиг. В данном случае он лежит в подпапке properties/ — это файл Develop или Production. В зависимости от того, что в них — будет конфигурироваться проект. Скажем, если мы разворачиваем проект строкой ant -Dbt=D install — подцепится Debug конфиг (=D). Если бы мы собирали его -Dbt=P — подцепился бы Production конфиг. Вот вам пример конфига — естественно, некоторые значения я там заменил :) А, кстати — вот вверху есть configure таргет — в нем происходит следующее. Сначала удаляется старый файл (который генерируется при развёртке проекта, скажем это файл с настройками). Затем, на его место пишется шаблон, простым копированием. И затем, в этом скопированном шаблоне заменяются токены вида #ANT:forum.settings.group.auth# на соответствующие (см. выше) значения. Итак, пример конфигурационного файла Develop:

#-----------------------------------------------------------------
# Properties for Dev-build
#-----------------------------------------------------------------

# Forum Settings
forum.name=Some Forum Name
forum.host=www.host.ru
forum.path=/www/host.ru
forum.install.path=/www/host.ru/htdocs
forum.locale=ru_RU.CP1251
forum.date_start=1234567890

forum.settings.group.banned=1
forum.settings.group.admin=2
forum.settings.group.guest=3
forum.settings.group.member=4
forum.settings.group.auth=5

# SVN Settings

forum.svn.user=svn-user

# Репозиторий движка. 
forum.svn.engine=svn+ssh://host.ru/ipb
forum.svn.engine.branch=branches/Dev_1.0.0
forum.engine.dir=${forum.path}/engine

# Проектный репозиторий
forum.svn.project=svn+ssh://host.ru/project
forum.svn.project.branch=Dev_0.0.3
forum.project.dir=${forum.path}/project

# uploads dir
forum.uploads.dir=${forum.path}/uploads

# DB Settings
forum.sql.driver=pgsql
forum.sql.host=host.ru
forum.sql.port=6505
forum.sql.database=forum
forum.sql.user=forum_user
forum.sql.pass=password
forum.sql.tbl_prefix=prefix_
forum.sql.debug=1

И файлик, собственно, с шаблоном для конфигурационного файла:

<?php
$INFO['sql_driver']		=	'#ANT:forum.sql.driver#';
$INFO['sql_host']		=	'#ANT:forum.sql.host#';
$INFO['sql_port']		=	'#ANT:forum.sql.port#';
$INFO['sql_database']		=	'#ANT:forum.sql.database#';
$INFO['sql_user']		=	'#ANT:forum.sql.user#';
$INFO['sql_pass']		=	'#ANT:forum.sql.pass#';
$INFO['sql_tbl_prefix']		=	'#ANT:forum.sql.tbl_prefix#';
$INFO['sql_debug']		=	'#ANT:forum.sql.debug#';
$INFO['board_start']		=	'#ANT:forum.date_start#';
$INFO['installed']		=	'1';
$INFO['php_ext']		=	'php';
$INFO['safe_mode']		=	'0';
$INFO['board_url']		=	'http://#ANT:forum.host#';
$INFO['banned_group']		=	'#ANT:forum.settings.group.banned#';
$INFO['admin_group']		=	'#ANT:forum.settings.group.admin#';
$INFO['guest_group']		=	'#ANT:forum.settings.group.guest#';
$INFO['member_group']		=	'#ANT:forum.settings.group.member#';
$INFO['auth_group']		=	'#ANT:forum.settings.group.auth#';

$INFO['locale']			=	'#ANT:forum.locale#';
?>

Да, и кстати. Если вы поменяли что-то в конфигурационном файле, либо заменили нечто в шаблоне — вам не обязательно (в рамках приведённого примера) выкатывать всё заново. Достаточно вызвать команду: ant -Dbt=D configure. В результате будет выполнен таргет configure, в котором произойдёт перегенерация всех шаблонно-генерируемых файлов, вот.

Вот, в общем-то и всё… Надеюсь, удалось осветить как и что. Хотя концовка получилась немного скомканной — дописывалось уже через пару недель, после того, как основной текст был порождён. Так что в спешке. Но если не дописать сейчас — то наверное уже не дойдут руки никогда :) Вот. Все вопросы, если они возникнут, как обычно — в каменты.

Автор

Алекс Разгибалов

Сумасшедший мужчина, неопределённого возраста, наслаждающийся манией преследования. Паталогически недоверчив, эгоистичен, авторитарен. Вторичные диагнозы - программист и поц. Владеет английским языком на уровне около хренового разговорного. Также знаком с некоторыми другими языками. Интересуется всем и вся, за счёт чего в любой области знания являются поверхностными, неглубокими. Характер невыдержанный. Крепость - 55 градусов.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Собирать идеально - не обязательно, просто приблизительно соберите картинку (должен быть включен JavaScript).WordPress CAPTCHA