Мы пройдём пять простых шагов, чтобы начать использовать Ant:
- Скачаем, установим и проверим.
- Напишем простой HelloWorld сценарий.
- Разберёмся с принципами работы и XML форматом сценария сборки.
- Узнаем минимально необходимый список заданий.
- Напишем сценарий для полного цикла сборки и тестирования учебного проекта.
Нам потребуется Java SE Development Kit (JDK, скачиваем по адресу www.oracle.com/technetwork/java/javase/downloads), ваш любимый текстовый редактор (в Linux – vi/vim/gedit, для Windows рекомендую Notepad++) и базовые навыки работы в командной строке. Сценарии сборки и примеры на Java протестированы в Linux (Simply Linux 7.95.0, CentOS Linux 6.8) и в Windows (XP/7). Использование Ant одинаково и в Linux и в Windows.
1. Скачиваем, устанавливаем, проверяем
Linux: устанавливаем из репозитария командой вроде sudo apt-get install ant (замените apt-get на yum если необходимо). Важно: нам нужна версия не ниже 1.8.*. В репозитарии CentOS 6.8 версия 1.7.1, поэтому лучше использовать скрипт, описанный в предыдущей статье.
Windows: посещаем веб-сайт ant.apache.org, заходим в раздел Download/Binary Distributions и скачиваем архив apache-ant-1.10.1-bin.zip (возможно сейчас есть уже более свежая версия). Содержимое архива копируем в любой каталог, например в «C:\Program Files\Apache Ant». Затем добавляем путь к каталогу bin (C:\Program Files\Apache Ant\bin) в системную переменную Path.
Проверяем работоспособность, вызвав ant в командной строке:
$ ant -version
Apache Ant(TM) version 1.10.1 compiled on February 2 2017
Если аналогичное сообщение получено – всё в порядке.
2. Пишем HelloWorld сценарий
<?xml version="1.0"?>
<project name="HelloWorld" default="hello">
<target name="hello">
<echo>Hello, World!</echo>
</target>
</project>
Создаём в домашнем каталоге подкаталог hello (в Linux это делает команда mkdir, в Windows – md) и сохраняем туда файл с именем build.xml, содержащий предложенный выше сценарий. Переходим в этот каталог и вызываем ant:
$ mkdir hello
$ cd hello
$ ant
Buildfile: /home/lamp/hello/build.xml
hello:
[echo] Hello, World!
BUILD SUCCESSFULL
Total time: 0 seconds
Что произошло? Ant нашел файл сценария с именем по умолчанию (build.xml) и выполнил target c именем hello, также указанный по умолчанию в теге project с помощью атрибута default (обратите внимание что в теге project мы ещё указали имя проекта, используя атрибут name). Мы получим такой же результат, если при вызове ant укажем в качестве параметра hello:
$ ant hello
3. Основные принципы работы
Сценарий сборки – обычный XML-файл. Текст открывается (и закрывается) тегом project, в котором можно указать имя проекта и цель по умолчанию. Далее он содержит определение целей (target), зависимостей (depends) и свойств (property). Простейший сценарий должен иметь хотя бы одну цель. В теге target мы описываем вызов одного или нескольких заданий (tasks). Для target можно задать имя с помощью атрибута name (name=«name_of_target»). Заданное имя становится командой для нашего сценария и вызвать соответствующий target можно так:
$ ant name_of_target
В target есть возможность указать зависимость с помощью атрибута depends. Зависимости связывают target'ы между собой. Например, есть target c именем “compile”, а есть – с именем “run”, зависимый от “compile”. И если мы захотим выполнить “run”, сначала выполнится “compile”.
4. Минимально необходимый список заданий (tasks)
Стандартная версия Ant содержит более 150 заданий (https://ant.apache.org/manual/tasklist.html). Нам пока потребуются только семь:
- echo – вывод сообщений в консоль
- mkdir – создание директорий
- delete – удаление файлов и директорий
- javac – компиляция Java–кода
- java – запуск class и jar файлов
- jar – создание jar файла
- junit – запуск тестов
5. Сценарий для сборки и тестирования Java проекта
Ant предоставляет полную свободу в формировании структуры каталогов. Создаём в нашем каталоге hello подкаталог src для исходных текстов на Java:
$ mkdir src
И сохраняем туда файл HelloWorld.java следующего содержания:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
А затем немного усложняем текст нашего сценария (build.xml):
<?xml version="1.0"?>
<project name="HelloWorld" default="run">
<target name="compile" depends="mkdir">
<mkdir dir="build/classes"/>
<javac destdir="build/classes" includeantruntime="false">
<src path="src"/>
</javac>
</target>
<target name="run" depends="compile">
<java classname="HelloWorld" classpath="build/classes"/>
</target>
<target name="clean">
<delete dir="build"/>
</target>
</project>
Теперь сценарий содержит три target (команды): compile (компиляция файла(ов) .java), run (запуск файла .class), clean (удаление папок с результатами компиляции). При этом compile содержит два tasks – mkdir и javac. Обратите внимание на зависимость: target run предварительно вызовет compile. Кроме того run – это target по умолчанию для проекта.
Запускаем сценарий без параметров и видим результат работы Java программы: Hello, World!
Прямое указание имен каталогов не говорит о хорошем стиле. Особенно если имена в сценарии повторяются. Модифицируем build.xml, используя property (обратите внимание, как нам пригодилось имя проекта, заданное в project) и добавив пару комментариев:
<?xml version="1.0"?>
<project name="HelloWorld" default="run">
<!-- define names of directories -->
<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="classes" location="${build}/classes"/>
<!-- define all targets -->
<target name="compile">
<mkdir dir="${classes}"/>
<javac srcdir="${src}" destdir="${classes}" includeAntRuntime="false"/>
</target>
<target name="run" depends="compile">
<java classname="${ant.project.name}" classpath="${classes}"/>
</target>
<target name="clean">
<delete dir="${build}"/>
</target>
</project>
Теперь добавим в сценарий target package для формирования jar файла:
<target name="package" depends="compile">
<jar destfile="${build}/${ant.project.name}.jar" basedir="${classes}">
<manifest>
<attribute name="Main–Class" value="${ant.project.name}"/>
</manifest>
</jar>
</target>
и убедимся что всё работает:
$ ant package
$ java -jar build/HelloWorld.jar
Hello, World!
Перейдём к тестированию. Изменим код проекта (чтобы было что тестировать):
public class HelloWorld {
public static void main(String[] args) {
HelloWorld hello = new HelloWorld();
System.out.println(hello.sayHello());
}
String sayHello() {
return "Hello, World!";
}
}
и добавим в каталог src файл/класс TestHello.java с простым тестом:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class TestHello {
@Test
public void testHello() {
HelloWorld hello = new HelloWorld();
assertEquals("Hello, World!", hello.sayHello());
}
}
Используя информацию со страницы https://github.com/junit-team/junit4/wiki/getting-started скачиваем два файла, junit-4.12.jar и hamcrest-core-1.3.jar и копируем их в каталог нашего JDK/jre/lib/ext (такую команду копирования используем в CentOS 6.8):
$ sudo cp ~/Downloads/*.jar /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.121-0.b13.el6_8.i386/jre/lib/ext/
Теперь можно проверить как работает тест в командной строке:
$ java -cp build/classes org.junit.runner.JUnitCore TestHello
JUnit version 4.12
.
Time: 0,281
OK (1 test)
Добавляем в наш сценарий ещё один target – test:
<target name="test" depends="compile">
<junit>
<classpath>
<pathelement location="${classes}"/>
</classpath>
<test name="TestHello"/>
</junit>
</target>
и дополняем строку в target package (jar):
<jar destfile="${build}/${ant.project.name}.jar" basedir="${classes}" excludes="Test*.class">
Теперь к списку команд нашего сценария (compile, run, package, clean) добавилась test.
В заключение меняем код нашего проекта так, чтобы приветствие выводилось в отдельном графическом окне. Затем формируем jar файл и запускаем его двойным кликом мыши (у вас должно быть настроено выполнение jar файлов по двойному клику).
Третья версия Java кода:
import javax.swing.*;
import java.awt.*;
public class HelloWorld extends JFrame {
public static void main(String[] args) {
new HelloWorld();
}
HelloWorld() {
setTitle(sayHello());
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(200, 200, 300, 200);
JLabel label = new JLabel(sayHello(), SwingConstants.CENTER);
label.setFont(new Font("", Font.BOLD, 24));
add(label);
setVisible(true);
}
String sayHello() {
return "Hello, World!";
}
}
Слегка дополняем сценарий (в target run), дописав fork=«true» (запуск выполнения класса в другой виртуальной машине). В противном случае run не сработает (проверено экспериментально):
<java classname="${ant.project.name}" classpath="${classes}" fork="true"/>
Выполняем команду формирования jar файла ($ ant package), открываем в проводнике файлов каталог ~/hello/build, находим там HelloWorld.jar, дважды кликаем по нему мышкой и получаем удовольствие от созерцания графического окошка с приветствием.
Комментарии (30)
kefirfromperm
04.03.2017 15:11Но статья будет не полной, если не упомянуть Apache Ivy
http://ant.apache.org/ivy/
ScratchBoom
04.03.2017 19:22+17Посмотрел в календарь. Вроде 2017 на дворе.
sshikov
04.03.2017 23:07+5Откровенно говооря, тоже не понял, нафига сегодня об этом писать. Кому-то нужен учебный материал? Ну так извините, вот же оно: оно, нет? При этом еще в 2010 написано — и более подробно.
GreyCat
05.03.2017 00:38+2При этом еще в 2010 году в комментариях все обсуждают, что ant уже безнадежно неактуален и смысла о нем говорить нет ;)
sshikov
05.03.2017 09:55+2Именно. Уже тогда это было ясно. А сегодня я бы лично за попытку применить ant для такого, как тут описано, уже бил бы по рукам.
Потому что если у вас типовой проект — maven, если что-то нестандартное, и хочется скриптов — gradle/sbt/что угодно из той же оперы, но не ant. Все что надо про него знать — что оно еще существует, потому что в свое время много понаписали.
Googolplex
05.03.2017 15:48+3Пожалуйста, не нужно продолжать использовать эту древнюю тулзовину. Есть гораздо более адекватные современности вещи; если хочется лёгкой императивности, то можно взят тот же Gradle.
Брр, как вспоминаю рекурсивные антовые сборки из сотен файлов по несколько тысяч строк...
gkraser
05.03.2017 17:01-2Ant, как инструмент, может и древний, но ведь работает. Молоток тоже не вчера изобрели. А зная, что такая штука имеется, можно облегчить себе жизнь. Например в gradle: https://docs.gradle.org/current/userguide/ant.html
aleksandy
05.03.2017 20:34+2junit-4.12.jar и hamcrest-core-1.3.jar и скопируем их в каталог нашего \jre\lib\ext
А потом в другом проекте, использующем другие версии артефактов, всё поломается.
Не надо ничего складывать ext-каталог, если без этого можно обойтись. А обойтись можно в 145% процентах случаев.biblelamp
06.03.2017 08:50Не надо ничего складывать ext-каталог, если без этого можно обойтись. А обойтись можно
Поделитесь — как бы вы поступили в данном случае?aleksandy
06.03.2017 10:26Предпочтительнее использовать ivy.
Без ivy, сделать каталог в проекте, в который складывать необходимые зависимости. При компиляции засовывать этот каталог в classpath. Тэг в ant для этого есть.biblelamp
06.03.2017 10:33использовать ivy
Спасибо, попробую. Что порекомендуете прочесть для быстрого старта?evnp
06.03.2017 14:49Официальную документацию, как и всегдаНет, лучше ничего про ivy не читать, почитай лучше про Gradle, можно даже на Хабре :)biblelamp
06.03.2017 14:51ок, спасибо, так и сделаю. Как вообще думаешь, можно что-то простое сделать с Gradle, чтобы показывать начинающим?
evnp
06.03.2017 15:03Я обычно использую его примерно так, чтоб утрамбовать внутрь все внешние зависимости. Если зависимостей нет, как в твоем случае, то все еще проще. может быть достаточно чего-нибудь в таком духе.
vba
06.03.2017 12:58Зашел, почитал, поностальгировал (эх бесшабашная молодость, джарки в либочке и или флешочкой обменивались или все в CVS/SVN зато можно было кофе пойти попить пока новый проект чекапился), закрыл, вернулся к домашнему уютному gradle(работа) и sbt(дом).
Alexls
07.03.2017 09:03-1А вот если необходимо продемонстрировать Заказчику сборку проекта из исходников на компьютере без интернета.
Куда тут без анта?grossws
07.03.2017 10:54Какая разница, таскать кусок
.m2/repository
илиlib
, если у вас внешние зависимости есть? А если нет, тоmvn clean install
.
evnp
07.03.2017 12:46C gradle все равно будет проще:
dependencies { compile fileTree(dir: 'lib', include: ['*.jar']) }
Это если вас не заломает вытягивать и раскладывать зависимости руками, иначе частичное зеркало maven (например, средствами nginx proxy_pass и proxy_store) тоже вполне себе вариант.
Alexls
08.03.2017 06:00-1Вот только все решения требуют наличия
1. всех зависимостей сложенных в кучу (каталог или репозиторий)
2. предварительно написанного скрипта
и в итоге получаем проблему попа и попадьи
kefirfromperm
Аж слеза прокатилась по щеке.
grossws
От воспоминаний о хороших нечитаемых
build.xml
на 5-10к строк?