Автоматизация процесса сборки java-проектов с помощью Ant.

Из данной статьи Вы узнаете, как с помощью Apache Ant framework автоматически собирать Java проект, запускать тесты и многое другое. Ant - это framework, с помощью которого можно избавиться от стандартного набора команд:
# javac ...
# java ...

которые приходится выполнять после изменений в коде. Для этого Вам нужно будет 1 раз настроить xml файл для проекта с соответствующими заданиями. А далее запускать его либо вручную, либо через crontab либо через CI(Continuous Integretion) систему.

Установка и настройка Ant

  1. Скачиваем последнюю версию Ant.
  2. Распаковываем архив в какую-нибудь папку, к примеру, /home/ant/apache-ant-1.8.2
  3. Прописываем переменные окружения:
    export ANT_HOME=/home/ant/apache-ant-1.8.2
    export PATH=$PATH:$ANT_HOME/bin
    export JAVA_HOME=/usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0
  4. Перечитываем настройки окружения:
    # source /root/.bash_profile

Основы Ant

Смысл использования Ant в том, что в зависимости от этапа разработки(цикла) Вам необходимо выполнять различные виды сборок. К примеру, просто откомпилировать классы, сделать проверку с помощью unit-тестов, сгенерировать javadoc. Или выполнить все одновременно. Этим и занимается Ant - он позволяет настроить файл с заданиями и их комбинациями, которые можно вызывать последовательно.
Концепции Ant :

Для запуска самого простого задания с помощью ant, необходимо создать файл build.xml в папке с проектом. И заполнить его:
<project name="EnglishTesting" default="test">
  <target name="test" description="Start test" >
    <echo>Performing of the tests....</echo>
  </target>
</project>
Таким образом мы создали задание "test" с одной командой "echo".
При запуске Ant созданного Вами задания приложение должно выдать что-то вроде:
# ant -f build.xml test
Buildfile: /home/masha/java/EnglishTesting/build.xml

test:
     [echo] Performing of the tests....   

BUILD SUCCESSFUL
Total time: 1 second
Называть задания можно как угодно, но лучше использовать следующие рекомендуемые наименования, которые для всех других разработчиков ясны и понятны: И некоторые другие. Подробнее можете прочитать, к примеру, в книге "Professional Java Tools for Extreme Programming".

Реализуем компиляцию проекта

Рассмотрим простое задание - скомпилируем наш проект в нужной директории. Для этого создадим следующий build.xml файл:

<project name="EnglishTesting" default="compile">
  <property name="src.java" location ="src"/>
  <property name="build.dir" location="build"/>
  <property name="build.classes.java" location="${build.dir}/class/java"/>
  <target name="make.dirs" description="Making  of the auxillary dirs" >
    <mkdir dir="${build.dir}"/>
    <mkdir dir="${build.classes.java}"/>
  </target>
  <target name="compile" description="Compilation of the classes" depends="make.dirs">
    <echo>Compilation ....</echo>  
    <javac includeantruntime="false"  destdir="${build.classes.java}">
      <src path="${src.java}"/>
    </javac>
  </target>
</project>
Как Вы заметили мы задали набор property , где указали директории исходников, релизов. Атрибут location (value) - хранит значение переменной. Если Вы создаете переменную для хранения имени директории, то нужно указывать location, если нужно использовать переменную в качестве имени проекта, то используйте value.
Далее мы выполняем задание make.dirs. В нем мы вызываем комады для создания нужных нам директорий. И только после этого мы выполняем задание compile, так как в нем есть атрибут depends. Атрибут depends позволяет нам управлять заданиями.
Команда javac позволяет собрать проект. Атрибут destdir указывает, где нужно расположить откомпилированные классы. Атрибут src path="${src.java}" указывает где взять исходники. includeantruntime - атрибут отвечающий за подключение библиотек лежащих в lib самого Ant. Лучше всегда указывать false, чтобы выполнение заданий не зависило от окружения - от машины на которой запускается build.xml.
Запустим build.xml файл:

# ant compile.java
Buildfile: /home/masha/java/EnglishTesting/build.xml

make.dirs:
    [mkdir] Created dir: /home/masha/java/EnglishTesting/build
    [mkdir] Created dir: /home/masha/java/EnglishTesting/build/class/java

compile.java:
     [echo] Compiling classes...
    [javac] Compiling 4 source files to /home/masha/java/EnglishTesting/build/class/java

BUILD SUCCESSFUL
Total time: 1 second

Заметьте, если повторно запустить на выполнение эту же команду, то будет выдано следующее:

# ant compile.java
Buildfile: /home/masha/java/EnglishTesting/build.xml

make.dirs:

compile.java:
     [echo] Compiling classes...

BUILD SUCCESSFUL
Total time: 0 seconds
Это значит, что если папки уже существуют, то они повторно не пересоздаются. А также если в коде не было изменений, то он также повторно не компилируется.

Тестирование проекта

Для запуска тестов используется тег junit. Он имеет множество удобных настроек. Рассмотрим запуск простого теста на следующем примере. Для этого допишем в наш предыдущий build.xml файл новую задачу test:

<target name="compile.test" description="Compilation of the tests" depends="make.dirs, compile">
    <echo>Compiling tests....</echo>  
    <javac includeantruntime="false"  destdir="${build.classes.test}">
      <src path="${src.test}"/>
	  <classpath>
        <pathelement location="${classpath.dir}/junit-4.5.jar"/>
		<pathelement location="${build.classes.java}"/>
        </classpath>
    </javac>
</target>
<target name="test" Start test EnglishTesting class" depends="compile.test">
    <junit printsummary="yes" haltonerror="yes" haltonfailure="yes" fork="yes">  
    
      <formatter type="plain" usefile="false"/>
	  <test name="testing.EnglishTestingTest"/>
	  <classpath>
        <pathelement location="${classpath.dir}/junit-4.5.jar"/>
		<pathelement location="${build.classes.java}"/>
		<pathelement location="${build.classes.test}"/>
        </classpath>
    </junit>
</target>
Свойства ${build.classes.test}, ${classpath.dir} зададим заранее. Теперь запустим наш файл сборки:
# ant test
Buildfile: /home/masha/java/EnglishTesting/build.xml

make.dirs:
    [mkdir] Created dir: /home/masha/java/EnglishTesting/build
    [mkdir] Created dir: /home/masha/java/EnglishTesting/build/class/java
    [mkdir] Created dir: /home/masha/java/EnglishTesting/build/class/test
    [mkdir] Created dir: /home/masha/java/EnglishTesting/build/jar

compile:
     [echo] Compiling classes...
    [javac] Compiling 4 source files to /home/masha/java/EnglishTesting/build/class/java

compile.test:
     [echo] Compiling tests....
    [javac] Compiling 2 source files to /home/masha/java/EnglishTesting/build/class/test


test:
    [junit] Running testing.EnglishTestingTest
    [junit] Testsuite: testing.EnglishTestingTest
    [junit] Tests run: 10, Failures: 0, Errors: 0, Time elapsed: 0.061 sec
    [junit] Tests run: 10, Failures: 0, Errors: 0, Time elapsed: 0.061 sec
    [junit]
    [junit] Testcase: testCalcPercentNullQuestions took 0.007 sec
    [junit] Testcase: testCalcPercent took 0.001 sec
    [junit] Testcase: testCalcSkillIncrement took 0.001 sec
    [junit] Testcase: testCalcSkillIncrementErrors took 0 sec
    [junit] Testcase: testGetResultsExcelent took 0.001 sec
    [junit] Testcase: testGetResultsGood took 0 sec
    [junit] Testcase: testGetEnglishWord took 0 sec
    [junit] Testcase: testCheckAnswer took 0.001 sec
    [junit] Testcase: testCheckIncorrectAnswer took 0 sec
    [junit] Testcase: testGenerateAnswerForImproperWord took 0 sec

BUILD SUCCESSFUL
Total time: 3 seconds
Рассмотрим атрибуты junit более подробно. Также можно задать и дополнительные параметры JVM, такие как maxmemory размер памяти и timeout время на выполнение тестов. Элемент test name позволяет указать нам имя класса с тестами для запуска. Так как unit-тесты зависят от внешних ресурсов, таких как сама библиотека junit.jar и исходников нашего проекта, то при их компиляции мы обязательно должны указывать элемент classpath. Т.е. указать junit, где искать необходимые библиотеки. Подключить classpath можно двумя способами:
1) как указано у нас
<javac ...>
  <classpath>
    <pathelement location="${classpath.dir}/junit-4.5.jar"/>
    <pathelement location="${build.classes.java}"/>
  </classpath>
</javac>	
2) либо
<project ... >
  <path id="project.class.path">
    <pathelement location="${classpath.dir}/junit-4.5.jar" />
    <pathelement location="${build.classes.java}"/>
  </path>
  <target ... >
    <javac ...>
      <classpath refid="project.class.path"/>
    </javac>
  </target>
</project>
Первый способ удобен, когда у Вас маленький проект и Вам нужно быстро набросать сборочный файл. Но лучше пользоваться вторым способом. Второй способ позволяет сэкономить, если придется указывать classpath и в других задачах java или javac.
Элемент formatter type отвечает за формат результатов прогона тестов. Значение type может быть brief - краткий текстовый, plain - подробный текстовый и xml. Атрибут usefile позволяет выводить результаты прогона тестов на консоль или в файл.

Генерация отчета html о прохождении тестов

Как говорилось выше результаты задания junit могут выводиться либо на консоль либо в файл. Для генерации html отчета необходимо создать задания следующего вида:

<target name="testsuite" description="Start TestSuite class" depends="compile.test">
  <junit printsummary="yes" haltonerror="yes" haltonfailure="yes" fork="yes">
    <formatter type="xml"/>
    <test name="suite.SuiteTests" todir="${html.report.dir}"/>
    <test name="testing.EnglishTestingTest" todir="${html.report.dir}"/>
    <classpath>
      <pathelement location="${build.classes.java}"/>
      <pathelement location="${build.classes.test}"/>
    </classpath>
  </junit>
</target>
<target name="report" description="" depends="testsuite">
  <mkdir dir="${html.report.dir}/html"/>
  <junitreport todir="${html.report.dir}" >
    <fileset dir="${html.report.dir}">
      <include name="TEST-*.xml"/>
    </fileset>
    <report todir="${html.report.dir}/html"/>
  </junitreport>
</target>
Первое, что мы делаем это сохраняем результаты проведенных тестов в XML. Далее мы используем элемент junitreport, он преобразует полученный xml в html в указанную в атрибуте todir директорию. Элемент fileset позволяет junitreport указать, какие именно xml файлы использовать для генерации html. Результаты в броузере можно посмотреть открыв index.html:

Сборка jar архива

Каждый JAR файл содержит в себе файл MANIFEST.MF, который лежит в директории META-INF. MANIFEST.MF это текстовый файл, который содержит информацию о JAR архиве, например версию, конфигурацию, запускаемый класс. Сгенерируем JAR файл для нашего приложения с помощью следующего задания:

<target name="jar" description="Creating jar after tests" depends="compile, test">
  <jar destfile="${build.jar}/EnglishTesting.jar" basedir="${build.classes.java}">
    <manifest>
      <attribute name="Main-Class" value="userconsole.ViewTesting"/>
      <attribute name="Class-Path" value="${classpath.dir}/mysql-connector-java-5.1.14-bin.jar"/>
      <attribute name="Created-By" value="(с) Mariya Sokunova"/>
    </manifest>
  </jar>
<target/>
В нашем случае будем генерировать JAR файл только после корректного прохождения unit-тестирования. В MANIFEST.MF зададим атрибуты:
"Main-Class" - указываем класс, который является входной точкой вашего приложения. Класс должен содержать метод main().
"Class-Path" - перечисляем все библиотеки, от которых зависит наше приложение.
"Created-By" - указываем, кем было создано приложение.
Теперь можно запустить наше приложение так:
java -jar EnglishTesting.jar

Запуск проекта

Если для проверки работоспособности Вашего приложения необходимо осуществляеть его запуск, то можно воспользоваться следующим заданием:

<target "run" description="Run application" depends="jar">
  <echo>Running application . . . </echo>
  <java jar="${build.jar}/EnglishTesting.jar" fork="true"/>
<target/>
Заметьте, если Вы используете консольное приложение, то оно запускается в отдельном процессе, а не в том в котором запущен Ant. Поэтому вводить данные через консоль при этом не удастся.

Генерация документации

Когда проект готов Вы возможно захотите сформировать для него документацию - javadoc. Если Вы используете соответствующие комментарии к классам, то сформировать документацию не сложно. Вам необходимо создать задание :

<target name="javadoc" description="Creating javadoc" depends="make.dirs">
  <echo>Generation javadoc . . . </echo>
  <javadoc destdir="${javadoc.dir}" sourcepath="${src.java}" author="true" version="true"
           packagenames="*" windowtitle="EnglishTesting" use="true" >
    <bottom>
      <![CDATA[<b>Sokunova Mariya, 2011<b>]]>
    </bottom>
  </javadoc>
</target>
После запуска задания Вы получите готовую javadoc документацию :

Полный пример build.xml можно взять здесь. Более подробно об инструменте Apache Ant можно почитать здесь.