Журнал LinuxFormat - перейти на главную

LXF92:Hardcore Linux

Материал из Linuxformat
Перейти к: навигация, поиск


Hardcore Linux Проверьте себя в проектах для продвинутых пользователей

Содержание

Ant: Упростим Java-проекты

Если вы используете Java, то без Ant вам не обойтись. Скотт Дуглас покажет, как упростить дистрибуцию и облегчить процесс Java-разработки.

Если вы используете Java не только для запуска Azureus, то, скорее всего, встречались с Apache Ant. Возможно, вы использовали его для компиляции скачанного Java-пакета или писали с его помощью файл сборки для ваших собственных проектов. Ant [англ. муравей, – прим. пер.]. стал инструментом де-факто для сборки всего на Java. Он берет на себя все труды по компиляции Java-проектов, и при правильном использовании управляет путями к классам и библиотеками.

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

На этом уроке мы рассмотрим и выполним некоторые задачи, но сначала нам надо удостовериться, что ваш простой файл проекта правильно структурирован: легче будет добавлять новые задачи.

Мой первый файл проекта

 <project name=”id3” basedir=”.”>
 <description>
Build file for id3 project
 </description>
 <property name=”src” value=”/home/art/projects/id3/src”/>
 <property name=”libs” value=”/home/art/projects/id3/libs”/>
<path id=”base.path”>
<pathelement path=”${classpath}”/>
<fileset dir=”${libs}”>
<include name=”**/*.jar”/>
</fileset>
</path>
<target name=”compile” description=”compile the source code”>
<echo>Compiling source</echo>
<javac classpathref=”base.path”
srcdir=”${src}”>
<include name=”org/**”/>
</javac>
</target>
</project>

Это довольно стандартный файл сборки Ant. Он поможет нам с компиляцией проекта и с расположением классов и библиотек, но мало с чем еще. И такая работа, конечно, пригодится, но почему бы файл не улучшить? Для начала неплохо создать свойства вверху файла и обращаться к ним по имени из задач (например, так: ${имяСвойства}). Однако это не очень переносимый вариант: что если мы хотим собрать наш проект на другой системе или даже просто в другом каталоге?

Переносимые свойства

Вот что нам надо сделать: сохранить все свойства, относящиеся к путям, в отдельном файле и просто указать Ant, чтобы он читал их из этого файла. Первый шаг к достижению этого – замена двух строк, находящихся в разделе Properties, на одну следующую:

<property file=”local.properties”/>

Для хранения наших свойств мы создали файл с именем local.properties в том же каталоге, что и наш файл сборки, и поместили в него все свойства, которые могут измениться. Это просто текстовый файл, хранящий пары вида Свойство=Значение; строки, начинающиеся с #, Ant игнорирует.

# Локальный файл свойств
# Содержит каталоги для сборки
src=/home/art/projects/id3/src
build=/home/art/projects/id3/build
libs=/home/art/projects/id3/libs
dist=/home/art/projects/id3/dist
etc=/home/art/projects/id3/etc
mainclass=org.sturgeon.Id3Renamer
jarfile=id3.jar
debug=true
fork=true
source=1.5

Поскольку наша задача «компиляции» уже использовала свойства, записанные в начале исходного файла, то для обращения к ним никаких изменений проводить не требуется. Однако, так же, как мы установили локальные пути, можно добавить свойства для других вещей.

Здесь, fork указывает Ant, использовать ли компилятор JDK внешним образом, source задает требуемый уровень исходного кода, а debug указывает Ant, следует ли включать отладочную информацию в файлы классов.

Использование внешнего файла свойств означает, что мы можем использовать тот же файл сборки на любых платформах без изменений – только для каждой установки надо предоставить файл local.properties. Вот задача компиляции с включенными новыми флагами свойств.

<target name=”compile” description=”compile the source code”>
<echo>Compiling source</echo>
<javac srcdir=”${src}”
destdir=”${build}”
classpathref=”base.path”
fork=”${fork}”
debug=”${debug}”
source=”${source}”/>
<include name=”org/**”/>
</javac>
</target>

Теперь мы можем добавить задачу для тестирования проекта – почти так же, как мы тестировали проект компиляции. Единственная существенная разница – надо будет изменить classpath так, чтобы он включал каталог сборки. Это не вопрос: мы уже определили базовый путь для проекта с помощью атрибута path. К нему можно обращаться внутри атрибута classpath, надо только добавить строчку каталога сборки. Стоит также упомянуть здесь использование fork: оно сообщает Ant, исполнять ли код в другой JVM (а не в той, в которой работает сам Ant). Если вы собираетесь посылать аргументы командной строки в JVM, установите его в Yes.

<target name=”test” description=”test run the project”>
<echo>Running project</echo>
<java classname=”${mainclass}”
fork=”${fork}”
dir=”${build}”>
<classpath>
<pathelement location=”${build}”/>
<path refid=”base.path”/>
</classpath>
</java>
</target>

Муравей на хозяйстве

Прежде чем упаковать наш код в JAR-файл, добавим-ка пару задач для упрощения структуры проекта. К сожалению, Ant не станет пылесосить за вас квартиру (зато если у вас есть Roomba [Roomba – роботпылесос, см. на сайте http://en.wikipedia.org/wiki/Roomba, – прим. пер.], вы можете удумать умные задачи для его управления), но зато создаст нам структуру каталогов. Для начала припасем задачу Init – с ней более или менее ясно: все, что она делает – создает каталог build (для размещения файлов классов) и dist (где разместится JAR-файл). Далее предусмотрим задачу Clean, чтобы удалить ранее созданные каталоги. Задача Clean полезна для сборки во время различных стадий проекта, так как позволяет «начать с нуля» в любой момент. Добавьте эти две задачи в файл сборки, и мы перейдем к задаче Jar.


<target name=”init” description=”initialise directories”>
<echo>Initialising directories</echo>
<mkdir dir=”${build}”/>
<mkdir dir=”${dist}”/>
</target>
<target name=”clean” description=”remove directories”>
<delete dir=”${build}”/>
<delete dir=”${dist}”/>
</target>

Основная задача Jar очевидна: здесь указываются свойства для каталога вывода, а также файлы, которые надо включить. Однако мы введем в нее зависимость и потребуем, чтобы сперва выполнялась задача компиляции.

<target name=”jar” description=”jar up the project”
depends=”compile”>
<echo>Jarring the project</echo>
<jar destfile=”${dist}/${jarfile}” basedir=”.”>
<fileset dir=”${build}” includes=”**/*.class”/>
</jar>
</target>

Теперь мы можем запустить наш проект прямо из JAR-файла – осталось указать пути к классам и удостовериться, что мы имеем все необходимые библиотеки. Еще полезнее будет JAR-файла, запускаемый отовсюду: тогда всего одним файлом мы предоставим возмож- ность запуска нашего проекта кому захотим.

Прежде чем создавать JAR-файл-всезнайку, необходимо раздобыть все файлы, которые мы собираемся в нем хранить, и поместить их в тот же каталог: это проще, чем писать Jar-задачу для копирования всех файлов из разных мест. Здесь мы используем каталог build для хранения файлов, а каталог dist для хранения завершенного JAR-файла. Нижеследующая задача ResrcCopy копирует содержимое каталогов etc и src в каталог build, который будет включен в JAR-файл. Каталоги содержат файлы свойств проекта и его исходный код.

Использование атрибута fileset dir во втором элементе copy означает, что мы копируем весь каталог и его содержимое. Это важно, так как позволяет установить верную структуру внутри JAR-файла.

 <target name=”copyrsrc” description=”копируем ресурсы для файла jar”>
 <echo>Copying resource files for jar</echo>
 <copy todir=”${build}/etc”>
 <fileset dir=”${etc}”/>
 </copy>
 <copy todir=”${build}/src”>
 <fileset dir=”${src}”/>
 </copy>
 </target>

Далее нам необходимо указать задаче Jar, чтобы мы хотим включить эти новые файлы. Для простоты ссылок и для ясности создадим новый атрибут patternset. Его структура похожа на структуру атрибута path, и включает символы подстановки для типов файлов, которые мы хотим вставить в результирующий JAR-файл.

 <patternset id=”jar.resources”>
 <include name=”**/*.class”/>
 <include name=”**/*.properties”/>
 <include name=”**/*.java”/>
 </patternset>

Теперь можно внести поправки в задачу Jar и сообщить, что хотим включить эти файлы. Вот первая часть:


 <target name=”jar” description=”jar up the project”
 depends=”compile, copyrsrc”>
 <echo>Jarring the project</echo>
 <jar destfile=”${dist}/${jarfile}” basedir=”${build}”>
 <patternset refid=”jar.resources”/>

Здесь мы просто изменили атрибут fileset задачи Jar, чтобы он обращался к нашему набору шаблонов. Итого, у нас есть JAR-файл, включающий классы, исходный код и файлы свойств нашего проекта. Но кое-чего не хватает. Одной из причин, по которой мы задумали сделать JAR-файл, была возможность запуска всего проекта из одного файла, без дополнительных библиотек, и здесь возникает небольшая проблема. Мы могли бы включить библиотечные JAR-файлы в гото- вый patternset, и они отлично бы разместились внутри нашего JAR- файла. Однако Java не позволяет обращаться ко вложенным JAR-файлам. Значит, библиотечные JAR-файлы в определение путей классов нашего JAR-проекта включать нельзя – придется распаковать библиотечные JAR-файлы, затем упаковать заново распакованные классы и их структуру каталогов в наш собственный JAR-файл. Делать это вручную прямо-таки мучительно, и вдобавок во время разработки довольно часто создается другой, новый JAR-файл проекта. Тут-то и приходит на помощь Ant: с помощью одного атрибута он может позаботиться для нас обо всем. Вот магическая строка:

 <zipgroupfileset dir=”${libs}” includes=”*.jar”/>

Как часть задачи Jar, эта строка велит Ant включить содержимое всех JAR-файлов в каталог libs собственного JAR-файла. Просто, но эффективно.

Последний шаг состоит в создании файла манифеста JAR, с парой атрибутов, которые помогут Java узнать, что делать с JAR-файлом. Ant-задача Jar содержит атрибут manifest, позволяющий их определить.

<manifest>
<attribute name=”Main-Class” value=”${mainclass}”/>
<attribute name=”Class-Path” value=”.”/>
</manifest>

Здесь мы можем определить главный класс, выполняемый при запуске JAR-файла (java -jar <jar_архив>), и путь к классам, которые JAR-файл должен использовать. Осталось только закрыть Jar-задачу и закончить цель:

</jar>
</target>

Теперь у нас есть JAR-файл, который может запустить любой обладатель JVM – в некоторых операционных системах это достигается двумя щелчками на нем.

JavaDoc стоит тысячи слов

Любой проект, который мы собираемся сделать открытым, должен иметь включенную документацию Javadoc. Она позволит будущему пользователю просмотреть API и понять, как все работает. Ant, разумеется, имеет задачу Javadoc специально для этого.

Вот довольно простая реализация задачи Javadoc:

<target name=”javadoc” description=”create javadocs for the project”>
<echo>Creating JavaDoc for project</echo>
<javadoc sourcepath=”${src}”
packagenames=”org.sturgeon.*”
destdir=”${docs}”>
</javadoc>
</target>

Задача указывает Ant создать Javadoc-документацию в каталоге docs для исходного кода, расположенного в каталоге src, для пакетов, располагающихся ниже org.sturgeon (org.sturgeon.id3, например). Нужно добавить этот новый каталог docs в нашу задачу Init:

<mkdir dir=”${docs}”/>

и задачу Clean:

<delete dir=”${docs}”/>

Нужно также включить документацию в наш JAR-файл, так что припишем пару строк в наш набор шаблонов jar.resources, чтобы предусмотреть HTML-файлы Javadoc:

<include name=”**/*.html”/>
<include name=”**/*.gif”/>

Чтобы скопировать комплект документации в каталог сборки, включаемый в JAR-файл, надо добавить еще один атрибут copy в задачу ResrcCopy:

 <copy todir =”${build}/docs”>
 <fileset dir=”${docs}”/>
 </copy>

Готово: теперь у нас есть JAR-файл, который не только является исполняемым, но и содержит исходный код и документацию Javadoc.

Упакован и отгружен

Если бы этот JAR-файл был EJB, разве не славно было бы, чтобы он автоматически развертывался на EJB-сервере? Положим, что наш удаленный сервер позволяет только безопасные соединения, и что для передачи JAR-файла требуется безопасный FTP. Как обычно, Ant припас задачу, способную это сделать, и называется она Scp. Однако, так как это одна из опциональных задач Ant, нужно добавить внешние библиотеки для ее активации. Расположение внешних библиотек описано на странице библиотек зависимостей в руководстве Ant; можно видеть, что для задачи Scp требуется файл jsch.jar со страницы http://www.jcraft.com/jsch/index.html. Скачав библиотечный файл, скопируйте его в каталог библиотек Ant (ANT_HOME/lib).

Далее нам необходимо добавить несколько переменных в файл local.properties: они укажут Ant, куда положить файл.

 user=art
 passwd=password
 backendserver=www.sturgeon.org
 remotedir=/opt/id3

Теперь мы можем добавить новую безопасную задачу FTP (sftp) в наш существующий файл сборки. Мы добавим в зависимости задачу Jar, так нам нужно, чтобы файл существовал до того, как мы сможем его куда-либо отослать!

 we need this file to exist before we can send it anywhere!
 <target name=”sftp” description=”send jar file to remote server”
depends=”jar”>
 <echo>Copying file to remote directory</echo>
 <scp trust=”yes” todir=”${user}:${passwd}@${backendserver}:${
 remotedir}”>
 <fileset dir=”${dist}”>
 <include name=”${jarfile}”/>
 </fileset>
 </scp>
 </target>

Как вы можете видеть, эта задача использует тот же подход, что и другие наши задачи: за начальным объявлением следует атрибут fileset, определяющий файлы, которые мы хотим использовать в задаче. Один из главных атрибутов задачи – trust, приказывающий Scp всегда доверять неизвестным узлам. Без него потребовалось бы определить файл knownhosts с доверенными узлами. Сконструируем атрибут todir используя следующий формат: user:password@server:directory. Внутри набора файлов мы просто укажем JAR-файл, который хотим переслать.

Полезный трюк, если вы посылаете JAR-файл на Jboss (или аналогичный сервер приложений) – использовать команду sleep. Если вы удаляете старый JAR-файл EJB и затем сразу же загружаете новый, то JBoss, возможно, не успеет удалить старый файл EJB перед тем, как получить новый – что может привести к недоразумениям. Чтобы этого избежать, мы можем просто использовать задачу Sleep между удалением старого EJB и копированием нового файла, чтобы дать шанс JBoss завершить удаление:

 <sleep seconds=”5”/>

Можно также определить время в минутах и часах, если ваш сервер особо важных приложений работает на ветхом оборудовании.

В описанной задаче мы посылаем файл через безопасный FTP. При желании использовать старый добрый FTP-стандарт мы могли бы использовать задачу FTP. Так как это тоже опциональная задача, пришлось бы установить общую сетевую внешнюю библиотеку Jakarta, которую требует задача Ant FTP для своего функционирования.

Я не знаю слова «помощь»

В качестве финального аккорда, создадим задачу Help (Помощь) – пусть выводит список всех возможных задач на консоль. Хотя это довольно просто, но очень полезно, особенно если вас интересуют промежуточные задачи, запускаемые из-за зависимостей.

<target name=”help” description=”display default help message”>
<echo>help - display this message</echo>
<echo>init - Initialise the directory structure</echo>
<echo>clean - Clean directory structure</echo>
<echo>compile - Compile the source code</echo>
<echo>test - Test run the project</echo>
<echo>jar - Create project jar file</echo>
<echo>javadoc - Create JavaDocs for the project</echo>
<echo>sftp - Send the Jar file to the remote directory</echo>
</target>

Было бы мило с нашей стороны предоставить помощь по умолчанию: если кто-то вызовет Ant с нашим файлом проекта, не указав задачу, то увидит справку. Для этого достаточно определить свойство default у атрибута project:

<coide>

 <project name=”id3” default=”help” basedir=”.”>

</code>

Все описанные задачи можно было выполнить и вручную, с помощью стандартных инструментов командной строки, типа cp и mv. Но стоит помнить, что написав эти задачи, вы можете вызвать их сколь угодно раз. Если по-умному использовать зависимости, сложный проект можно разбить на ряд маленьких понятных команд. Так как хлопот при создании нового проекта немало, то возможность запуска одной задачи Ant, охватывающей множество разных аспектов проекта, явля- ется манной небесной. LXF

Еще один изящный инструмент

Можно предположить, что Apache Ant получил такое имя, потому что муравьи очень хорошие строители. Изначально предполагалось, что он заменит make во время сборки движка сервлетов Apache Tomcat, для борьбы с проблемами, с которыми автор столкнулся при использовании make. Так как Ant написан на Java (в отличие от make), то он позволяет вести настоящую кросс-платформенную разработку, требуя только совместимую JRE. Ant стал стандартным методом для сборки Java-проектов, и вы обнаружите, что большая часть открытых проектов на Java поставляется с файлом сборки для Ant.

Установка состоит из разархивирования двоичного дистрибутива в нужный каталог. При компиляции из исходных текстов Ant использует интересный подход: сначала частично компилирует себя, а затем использует этот получившийся кусок для компиляции главного двоичного кода Ant. Apache Ant доступен для скачивания с http://ant.apache.org по лицензии Apache Software License.

Пробуем озадачиться

Существует много сторонних задач для Ant, которые вы можете попробовать. Вот список самых интересных:

  • Ant2Svg (http://www.spiritedsw.com/ant2svg) Создаст простое графическое представление вашего файла сборки в формате SVG.

определенных в файле сборки.

Linguine Maps

Linguine Maps нарисует представление файла сборки Java.

Персональные инструменты
купить
подписаться
Яндекс.Метрика