Eclipse: знакомство с Ant

HTML и CSSXSLTJavaScriptИзображенияСофтEtc
Сергей Чикуенок

4 марта 2009


Задача.

Научиться выполнять рутинную работу с помощью компьютера.

Когда речь идет об использовании интегрированной среды разработки (IDE), часто приходится сталкиваться с мнением о том, что ее функционал слишком избыточен для решения повседневных задач. Доля правды в этом есть: появляются новые сущности, незнакомые механизмы, много настроек, о которых раньше не приходилось задумываться. Однако не стоит забывать, что работа в IDE — совершенно новый уровень разработки и многие привычные задачи в ней решаются по-другому. Прелесть интегрированной среды в том, что у разработчика есть возможность собрать под одной крышей (то есть в одной программе) много разных инструментов, которые будут не только решать разные задачи, но и тесно взаимодействовать друг с другом. Таким образом, перед разработчиком откроются абсолютно новые способы работы над проектом.

В прошлых статьях я рассказывал об инструментах для работы с исходным кодом, сейчас начнем знакомиться с приемами работы над проектом в целом. И начнем с автоматизации рутинных задач с помощью Ant.

Apache Ant известен любому Java-программисту: это популярный инструмент сборки ПО (build tool), полностью написанный на Java. Его поддерживают все IDE: NetBeans, IntelliJ IDEA, Eclipse. Сценарий сборки Ant представляет собой простой XML-файл. Несмотря на всю свою Java-направленность, и обычные веб-разработчики пользуются этим инструментом для решения своих задач.


Hello world!

Давайте начнем изучение Ant с классического примера: текст Hello world! выведем в консоль. Для начала нам нужно убедиться, что файл build.xml (так обычно называют сценарии сборки) привязан к редактору Ant в Eclipse. Идем в Preferences → General → Editor → File Associations и ищем там файл build.xml, привязанный к редактору Ant Editor. Если такой записи нет, добавьте ее: сначала нажмите Add напротив File types и введите туда build.xml, затем выделите только что добавленную запись и нажмите Add напротив Associated editors и в списке выберите Ant Editor. Выделите Ant Editor и нажмите кнопку Default.



  1. Создадим новый проект и назовем его, например, ant-test.
  2. В этом проекте создадим новый файл с именем build.xml. Файл должен сразу же открыться в Ant Editor.
  3. Запишем туда вот такой сценарий:
    01 
    02 
    03 
    04 
    05 
    06 
    <?xml version="1.0"?>
    <project name="my_test_project">
    	<target name="hello-world">
    		<echo>Hello world!</echo>
    	</target>
    </project>
    
  4. Откроем вид Ant: Window → Show View → Other...


  5. В только что открывшемся виде нажимаем на иконку , находим файл build.xml и дважды кликаем по нему. Теперь в этом виде отображается наш сценарий, который очень удобно тестировать.
  6. Раскроем список my_test_project и сделаем двойной клик по hello-world. В консоли должна появится надпись Hello world!



Рассмотрим подробнее структуру сценария. Родительским элементом является <project> с атрибутом name — это название сценария построения.

Внутри тега <project> находится тег <target>, внутри которого — <echo>. Тут мы подходим к двум важным понятиям Ant, которые поначалу могут немного запутать: это цели (targets) и задания (tasks). В данном случае мы выполнили цель hello-world (обязательный атрибут name у тега <target>) с помощью задания <echo>. Чтобы легче было запомнить, предлагаю пользоваться следующими правилами: цель указывает, что именно нужно сделать, а задания — с помощью чего достигается цель.

Чтобы лучше освоиться с Ant, давайте создадим что-нибудь полезное, например, актуальное на сегодняшний день сжатие набора js-файлов с помощью YUICompressor.


Поставим себе задачу. У нас есть набор JavaScript-файлов, представляющих собой одну библиотеку. Чтобы эта библиотека загрузилась максимально быстро для пользователя, нужно объединить все файлы в один и сжать результат с помощью YUICompressor. Создадим в нашем проекте ant-test две папки: js_src и js. В первой будем хранить исходник, а во второй — сжатый результат. Создадим несколько файлов в папке js_src: file1.js, file2.js и file3.js, в каждый из этих файлов напишем какой-нибудь код.

Приступим к написанию сценария. Определимся, каких целей мы хотим достичь. Нам нужно построить библиотеку lib.js (первая цель), для чего нужно объединить все файлы в один (вторая цель) и сжать их (третья цель). Вот примерный сценарий:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
<?xml version="1.0"?>
<project name="Build first JS lib" default="build-lib" basedir=".">

 	<target name="build-lib" depends="concat-files, minify">
		<echo>Done.</echo>
	</target>

	<target name="concat-files">
		<echo>Concatenating files</echo>
		<concat destfile="./js/lib.js">
			<filelist dir="./js_src">
				<file name="file1.js"/>
				<file name="file2.js"/>
				<file name="file3.js"/>
			</filelist>
		</concat>
	</target>

	<target name="minify">
		<echo>Minifying files</echo>
	</target>

</project>

Сразу рассмотрим все новые элементы и атрибуты. У элемента <project> появился атрибут default — это цель или набор целей, которые будут выполняться по умолчанию при вызове этого сценария. Нам это понадобится чуть позже. Атрибут basedir задает директорию, относительно которой будут считаться все относительные пути к файлам и директориям. Его указывать необязательно, по умолчанию он равен абсолютному пути к проекту, в котором находится сценарий.

Обратите внимание на атрибут depends у цели build-lib. Он определяет цели, от которых зависит текущая цель. В данном случае мы говорим: «Выполнить цель build-lib только после того, как будут выполнены concat-files и minify». Это очень удобный (но далеко не единственный) механизм для использования одинаковых последовательностей заданий в разных целях. Особенность зависимостей Ant заключается в том, что одна цель будет выполнена только один раз в течение сессии. Подробнее об этом читайте в документации.


Как видно из примера, у нас есть точка входа — цель build-lib (атрибут default у  <project>), которая зависит от двух других целей: concat-files и  minify. Объединение (конкатенация) файлов выполняется с помощью задания <concat>, в атрибуте destfile указываем, в какой файл хотим сохранить результат. За то, какие именно файлы и в какой последовательности должны быть объединены, отвечает файловый ресурс <filelist>, который мы отдаем заданию <concat>. Думаю, здесь все понятно: в атрибуте dir указываем, в какой директории нужно искать файлы, а с помощью элементов <file> задаем конкретные файлы. Типов ресурсов существует довольно много, большинство из них нужны Java-программистам. Для нас наиболее востребованными будут <filelist> и <fileset>. Основное их различие в том, что в первом случае указывается четкий набор файлов, а во втором — задается набор файлов с помощью паттернов включения и исключения. Например, сейчас мы могли бы заменить <filelist> на такую конструкцию:

01 
02 
03 
<fileset dir="./js_src">
	<include name="*.js"/>
</fileset>

Но у этого способа есть ряд недостатков. Во-первых, в набор попадут абсолютно все файлы с расширением js в папке js_src, что не всегда нужно на реальных проектах. А во-вторых, даже если указать в паттернах названия файлов, этот ресурс не гарантирует тот порядок следования, в котором вы хотите их объединить. То есть если важно, в каком порядке должны объединяться файлы, лучше задавать набор через <filelist>.

В приведенном примере смущает то, что пути к папкам жестко зашиты в код сценария, поэтому давайте вынесем их в отдельные свойства:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
25 
<?xml version="1.0"?>
<project name="Build first JS lib" default="build-lib" basedir=".">
	
	<property name="src.dir" value="./js_src"/>
	<property name="dest.dir" value="./js"/>
	
	<target name="build-lib" depends="concat-files, minify">
		<echo>Done.</echo>
	</target>

	<target name="concat-files">
		<echo>Concatenating files</echo>
		<concat destfile="${dest.dir}/lib.js">
			<filelist dir="${src.dir}">
				<file name="file1.js"/>
				<file name="file2.js"/>
				<file name="file3.js"/>
			</filelist>
		</concat>
	</target>

	<target name="minify">
		<echo>Minifying files</echo>
	</target>

</project>

Свойства в Ant задаются с помощью тега <property>. Свойства очень похожи на константы в языках программирования: задав один раз, вы больше не сможете поменять их во время исполнения сценария. Обращаться к свойствам следует через конструкцию ${название свойства}. Свойства можно задавать не только внутри <project>, но и внутри <target>, а также глобально для всего IDE через Preferences → Ant → Runtime → Properties.


Хозяйке на заметку

Не зря в начале статьи отдельно подчеркивалось, что файл build.xml открывается через Ant Editor — этот редактор имеет очень удобный code complete, который работает со свойствами (<property>), целями (в атрибутах depends, default и так далее), а также с доступными задачами и их атрибутами. Не забывайте об этой помощи, когда будете писать свои сценарии.

Проверьте работу сценария: сделайте двойной клик в Ant View на цели build-lib. У вас в папке js должен появиться файл lib.js, в котором будет содержимое всех трех файлов. Если файл не появился, обновите (Refresh) папку js в Project Explorer.

Файлы объединять мы научились, теперь научимся сжимать их с помощью YUICompressor. Сам разработчик этой замечательной библиотеки рекомендует вызывать компрессор как внешнюю программу, мы же поступим куда более интересно: создадим отдельную задачу, которая будет сжимать файлы.

Модульная структура Ant позволяет расширять его, дописывая новые задачи на Java. Сейчас мы научимся одному из способов добавления таких задач в сценарий сборки.

  1. Скачайте YUICompressor и распакуйте куда-нибудь архив, например, в папку C:\yuicompressor.
  2. Скачайте YUIAnt.jar и положите его в папку build, где вы распаковали YUICompressor (C:\yuicompressor\build).
  3. Добавьте вот такую конструкцию в сценарий внутрь тега <project>:
  4. 01 
    02 
    03 
    04 
    05 
    06 
    07 
    <taskdef name="yuicompress" classname="com.yahoo.platform.yui.compressor.YUICompressTask">
    	<classpath>
    		<fileset dir="C:\yuicompressor\build">
    			<include name="*.jar"/>
    		</fileset>
    	</classpath>
    </taskdef>
    

С помощью нее мы создали новую задачу <yuicompress>. Чтобы она работала, нужно указать полный путь к Java-классу этой задачи (com.yahoo.platform.yui.compressor.YUICompressTask), а также указать путь, где находится файл с этим классом (<classpath>/<fileset>). Теперь можно дописать цель minify:

01 
02 
03 
04 
05 
06 
07 
08 
<target name="minify">
	<echo>Minifying files</echo>
	<yuicompress munge="yes" linebreak="5000" preserveallsemicolons="yes" outputfolder="${dest.dir}">
		<fileset dir="${dest.dir}">
			<include name="*.js"/>
		</fileset>
	</yuicompress>
</target>

Мы вызвали задачу <yuicompress>, указав ей набор файлов, которые необходимо сжать, с помощью конструкции <fileset>. Так как мы используем задачу, а не вызов внешнего файла, Ant Editor сам подхватил нужный класс и теперь будет выдавать code complete по атрибутам задачи <yuicompress>. Итоговый сценарий сборки выглядит так:

01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
<?xml version="1.0"?>
<project name="Build first JS lib" default="build-lib" basedir=".">

	<property name="src.dir" value="./js_src"/>
	<property name="dest.dir" value="./js"/>

	<taskdef name="yuicompress" classname="com.yahoo.platform.yui.compressor.YUICompressTask">
		<classpath>
			<fileset dir="C:\yuicompressor\build">
				<include name="*.jar"/>
			</fileset>
		</classpath>
	</taskdef>

	<target name="build-lib" depends="concat-files, minify">
		<echo>Done.</echo>
	</target>

	<target name="concat-files">
		<echo>Concatenating files</echo>
		<concat destfile="${dest.dir}/lib.js">
			<filelist dir="${src.dir}">
				<file name="file1.js"/>
				<file name="file2.js"/>
				<file name="file3.js"/>
			</filelist>
		</concat>
	</target>

	<target name="minify">
		<echo>Minifying files</echo>
		<yuicompress munge="yes" linebreak="5000" preserveallsemicolons="yes" outputfolder="${dest.dir}">
			<fileset dir="${dest.dir}">
				<include name="*.js"/>
			</fileset>
		</yuicompress>
	</target>
</project>

Проверьте работу цели build-lib, в папке js у вас должен оказаться пожатый lib.js.


Сборка проекта

А теперь перейдем к самому интересному. Удобство интеграции Ant с Eclipse заключается не в том, что вы можете в любой момент вызвать нужную цель из окошка, а в том, что вы можете указать сборщик для проекта.



Зайдите в свойства проекта ant-test (Project → Properties) и перейдите в пункт Builders. Нажмите кнопку New и в диалоговом окне выберите Ant Builder. В появившемся окне задайте какое-нибудь имя вашему сборщику (например, My first Ant build). Теперь нужно выбрать сценарий (buildfile). Нажмите на кнопку Browse workspace и в проекте ant-test выберите файл build.xml. Перейдите на вкладку Refresh и отметьте пункты Refresh resources upon completition и The project containing the selected resource. Это значит, что после работы сборщика будет автоматически обновляться список файлов проекта, поэтому в Project Explorer вы всегда будете видеть актуальную версию проекта. Перейдите во вкладку Targets, там вы увидите список целей, которые будут выполняться при разных типах сборки. В частности, в разделах After a «Clean» и Manual build будет указано default target selected. Это означает, что будет выполняться цель, указанная по умолчанию у проекта (помните атрибут default у <project>?). Давайте еще укажем цель в Auto build, для этого нажмем на кнопку Set targets cправа от Auto Build и сразу нажмем на ОК — будет выбрана цель по умолчанию.

Теперь мы можем вызывать сборщик по нажатию на Project → Build Project (на эту команду можно повесить горячие клавиши). А если выберем опцию Project → Build Automatically, то нам даже ничего делать не надо: проект будет сам собираться при любом изменении любого файла проекта.



Таким образом, мы пришли к абсолютно прозрачной автоматизации рутинных задач при работе в IDE. Все, что необходимо сделать, — создать простой сценарий, указать его в качестве сборщика и включить опцию автоматической сборки. Каждый раз, когда вы модифицируете проект (редактируете, удаляете и добавляете файлы) все необходимые действия будут выполнятся в фоновом режиме, не отвлекая разработчика. Это особенно полезно при работе в команде: любой разработчик может внести изменения в сценарий, которые будут автоматически выполняться у всех остальных.

В этой статье были поверхностно рассмотрены некоторые базовые приемы работы с Ant. Советую ознакомиться со списком стандартных задач, там есть такие полезные вещи, как синхронизация каталогов, закачка файлов по FTP, выполнение SQL-запросов, XSL-трансформации, архивация файлов и многое другое.



В следующей статье мы продолжим изучение Ant — в частности, научимся писать дополнительные задачи на JavaScript. В качестве домашнего задания предлагаю сделать следующее: вынесите определение задачи <yuicompress> в отдельный файл и подключите его к сценарию (подсказка: воспользуйтесь заданием <import>, а также задайте глобальное свойство, в котором будет храниться путь к файлу с определением).



Посмотреть на Ant в действии можно в студийной лекции для технологов.



Дополнительные материалы:

— Скринкаст (Квиктайм, 101 МБ)