Ant

Koziołek

     1 Wstęp
     2 Struktura projektu
     3 Cele i zadania
          3.1 Cel (<em>target</em>)
          3.2 Zadanie (<em>task</em>)
          3.3 Przykładowe zadania wbudowane
     4 Parametryzowanie zadań
          4.4 Zmienne w pliku build.xml
          4.5 Plik build.properties
     5 Przykłady
          5.6 Usuwanie starych plików
          5.7 Tworzenie katalogów
          5.8 Kompilacja źródeł i testów
          5.9 Uruchomienie testów
          5.10 Tworzenie pakietów
          5.11 Tworzenie dokumentacji
          5.12 Wszystko naraz
1 Zobacz też

Wstęp

Apache Ant jest narzędziem wspomagającym i automatyzującym proces kompilacji. Umożliwia definiowanie skryptów za pomocą języka XML, które to skrypty wykonują zadania. Cytując dokumentację:

Apache Ant is a Java-based build tool. In theory, it is kind of like make, without make's wrinkles.

Można zatem założyć, że Ant powinien być pierwszym narzędziem, jakie będzie nam służyć w trakcie kompilowania naszych programów.

Struktura projektu

Ant jest narzędziem wymagającym stosunkowo prostej struktury projektu. Po ściągnięciu go ze strony należy skonfigurować zmienną ANT_HOME, tak by wskazywała na katalog, w którym zainstalowano Anta, oraz do zmiennej PATH dodać ścieżkę $ANT_HOME/bin (w systemie MS Windows: %ANT_HOME%/bin). Następnie należy sprawdzić, czy wszystko jest prawidłowo skonfigurowane:

$ ant
Buildfile: build.xml does not exist!
Build failed

Jak widać, by utworzyć projekt, wystarczy w katalogu umieścić plik build.xml. Jest to plik, w którym definiujemy właściwości projektu. Najprostsza jego wersja wygląda tak:

<?xml version="1.0" encoding="iso-8859-2"?>
<project name="Project name" basedir="." default="clean">
   <!--  -->
</project>

gdzie poszczególne atrybuty elementu <project> oznaczają:

  • name – nazwa projektu; opcjonalna;
  • basedir – ścieżka bazowa, od której są liczone wszystkie ścieżki względne; też opcjonalna, ale domyślnie jest to ścieżka do folderu z plikiem build.xml;
  • default – nazwa domyślnego zadania, które będzie wykonane, jeżeli nie podamy innych zadań do wykonania. Od wersji 1.6 biblioteki każdy projekt posiada domyślnie skonfigurowane wszystkie zadania dostarczone razem z biblioteką.

Jak łatwo zauważyć, Ant pozwala na bardzo swobodną konfigurację projektu. Niestety, w starszych wersjach biblioteki brak konfiguracji domyślniej wymuszał tworzenie dużej ilości "domyślnego" kodu. Obecnie nie jest to aż tak widoczne, ale często również trzeba dużo napisać, by odpowiednio skonfigurować projekt.

Do prawidłowego działania wystarczy JDK w wersji 1.2, ale producenci rekomendują użycie JDK w wersji 1.5.

Cele i zadania

Projekt w Ant jest podzielony na cele, które zawierają zadania. Cele definiujemy sami, a zadania są dostarczone wraz z biblioteką; można też samemu stworzyć zadanie poprzez rozszerzenie klasy Task.

Cel (target)

Cel jest logicznym krokiem w procesie kompilacji. Składa się z zadań i opisuje jakiś etap projektu. Przykładem celu może być kompilacja, umieszczenie na serwerze, wykonanie testów czy też synchronizacja z SVN.

W naszym projekcie przygotujemy wszystko od podstaw, wykorzystując cele i zadania. Stwórzmy więc katalog z plikiem build.xml i dodajmy pierwsze zadanie:

<?xml version="1.0" encoding="iso-8859-2"?>
<project name="Project name" basedir="." default="create">
	<description>Tutorial ANT na http;//4programmers.net/java/Ant</description>

	<!-- =================================
          target: create
         ================================= -->
	<target name="create" depends="" description="Tworzy strukturę projektu">
	
	</target>
</project>

Cel posiada obowiązkową nazwę i opcjonalny opis; może też zależeć od innych celów (np. tworzenie archiwum JAR zazwyczaj musi być wykonane po skompilowaniu kodu). Cele, od których zależy, definiujemy za pomocą atrybutu depends, w którym poddajemy nazwy celów rozdzielone przecinkiem.

Dodajmy do naszego celu kilka zadań, które będą tworzyć strukturę naszego projektu.

Zadanie (task)

Zadanie jest pojedynczą operacją, wykonywaną by osiągnąć cel. Może być to na przykład stworzenie katalogu, wywołanie kompilatora lub wypisanie czegoś do konsoli. Ilość zadań w Ancie jest ogromna. Biblioteka posiada około 70 wbudowanych zadań, a istnieją jeszcze możliwości zarówno ściągnięcia dodatkowych bibliotek, jak i stworzenia własnego rozwiązania.

Każde zadanie posiada unikalną listę parametrów, które są przekazywane jako atrybuty lub elementy XML. Sposób konfiguracji jest więc dość swobodny. Z jednej strony zapewnia to dużą elastyczność, ale z drugiej wymaga częstego zaglądania do dokumentacji w celu sprawdzenia, jak skonfigurować taki czy inny cel. Poniżej widzimy nasz plik build.xml wzbogacony o definicję zadań, po wykonaniu których zostanie stworzona struktura projektu:

<?xml version="1.0" encoding="iso-8859-2"?>
<project name="Project name" basedir="." default="create">
	<description>Tutorial ANT na http;//4programmers.net/java/Ant</description>

	<!-- =================================
          target: create
         ================================= -->
	<target name="create" depends="" description="Tworzy strukturę projektu">
		<echo>Tworzę katalog ze źrodłami</echo>
		<mkdir dir="./src/pl/koziolekweb/programmers/ant"/>
		<echo>Tworzę katalog z testami</echo>
		<mkdir dir="./test/pl/koziolekweb/programmers/ant"/>
		<echo>Tworzę katalog ze skompilowanymi klasami</echo>
		<mkdir dir="./bin/classes/"/>
		<echo>Tworzę katalog ze skompilowanymi testami</echo>
		<mkdir dir="./bin-test"/>
	</target>
</project>

Przykładowe zadania wbudowane

W powyższym przykładzie wykorzystaliśmy dwa zadania wbudowane. Pierwsze z nich, echo, pozwala na wypisanie komunikatu na ekranie. Drugie, mkdir, tworzy katalogi, jeżeli nie istnieją. Zadania wbudowane w większości odpowiadają potrzebom projektów. Do najpopularniejszych należą javac (uruchamiające kompilator), jar (tworzące archiwum) i javadoc (służące do generowania dokumentacji). Innymi przydatnymi zadaniami są copy (kopiujące pliki), delete (usuwające pliki) i zip (pakujące pliki).

Parametryzowanie zadań

Nasze zadania zazwyczaj będą współdzielić niektóre informacje. Są to przede wszystkim dane o ścieżkach z kodem źródłowym, nazwach plików JAR/WAR/EAR oraz ścieżkach do bibliotek. Warto trzymać je w jednym miejscu i mieć możliwość szybkiej ich edycji. Ant udostępnia dwie metody pozwalające na osiągnięcie tego celu. Pierwszą z nich jest możliwość definiowania zmiennych bezpośrednio w pliku build.xml, a drugą użycie pliku build.properties. W obu przypadkach odwołanie do wartości zmiennej następuje za pomocą ${nazwa_zmiennej}

Zmienne w pliku build.xml

Zmienne możemy definiować w pliku build.xml za pomocą specjalnego elementu <property/>. Na przykładzie naszego pliku konfiguracyjnego będzie to wyglądać następująco:

<?xml version="1.0" encoding="iso-8859-2"?>
<project name="Project name" basedir="." default="create">
	<description>Tutorial ANT na http;//4programmers.net/java/Ant</description>

	<property name="src" value="./src"/>
	<property name="src.test" value="./test"/>
	<property name="target" value="./bin/classes/"/>
	<property name="target.test" value="./bin-test"/>
	<property name="main.package" value="pl.koziolekweb.programmers.ant"/>
	<property name="main.package.dir" value="pl/koziolekweb/programmers/ant"/>

	<!-- =================================
          target: create
         ================================= -->
	<target name="create" description="Tworzy strukturę projektu">
		<echo>Tworzę katalog ze źrodłami</echo>
		<mkdir dir="${src}/${main.package.dir}"/>
		<echo>Tworzę katalog z testami</echo>
		<mkdir dir="${src.test}/${main.package.dir}"/>
		<echo>Tworzę katalog ze skompilowanymi klasami</echo>
		<mkdir dir="${target}"/>
		<echo>Tworzę katalog ze skompilowanymi testami</echo>
		<mkdir dir="${target.test}"/>
	</target>
</project>

Metoda ta jest dobra, ale jedynie wtedy, gdy wartości zmiennych są takie same dla wszystkich osób pracujących przy projekcie. Jeżeli zmienna wymaga różnych wartości – np. w zależności od środowiska lub osoby, która uruchamia proces – to niewskazane jest zmienianie jej za każdym razem. Jest to szczególnie uciążliwe w przypadku pracy z systemami kontroli wersji. Z jednej strony nie należy wysyłać do repozytorium pliku z wiadomościami przydatnymi tylko nam, ale z drugiej strony należy posiadać zawsze aktualny plik build.xml. W takim przypadku należy użyć pliku build.properties, zamiast bezpośrednich wpisów w build.xml.

Plik build.properties

Plik ten jest typowym plikiem .properties ze wszystkimi jego zaletami i wadami (kodowanie US-ASCII, patrz: Properties - pliki tekstowe). Przenieśmy część naszych zmiennych do tego pliku:

target=./bin/classes/
target.test=./bin-test/

Jak widać, nie ma różnicy pomiędzy tymi metodami definiowania zmiennych. Plik build.properties może być już wyjęty spod kontroli wersji i nie ma potrzeby zaśmiecania repozytorium różnymi wersjami konfiguracji. Wystarczy tylko podlinkować nasz plik w pliku build.xml i gotowe.

<?xml version="1.0" encoding="iso-8859-2"?>
<project name="Project name" basedir="." default="create">
	<description>Tutorial ANT na http;//4programmers.net/java/Ant</description>

	<property file="build.properties" />
	<property name="src" value="./src" />
	<property name="src.test" value="./test" />
	<property name="main.package" value="pl.koziolekweb.programmers.ant" />
	<property name="main.package.dir" value="pl/koziolekweb/programmers/ant" />

	<!-- =================================
          target: create
         ================================= -->
	<target name="create" depends="configure" description="Tworzy strukturę projektu">
		<echo>Tworzę katalog ze źrodłami</echo>
		<mkdir dir="${src}/${main.package.dir}" />
		<echo>Tworzę katalog z testami</echo>
		<mkdir dir="${src.test}/${main.package.dir}" />
		<echo>Tworzę katalog ze skompilowanymi klasami</echo>
		<mkdir dir="${target}" />
		<echo>Tworzę katalog ze skompilowanymi testami</echo>
		<mkdir dir="${target.test}" />
	</target>
</project>

Przykłady

Omówię teraz przykładowy plik build.xml, który zawiera kompletny zestaw najpopularniejszych zadań. Szczególnie polecam jego analizę osobom, które chcą wykorzystywać Anta jako narzędzie przy tworzeniu projektów na studiach. Zadania zostały tak przygotowane, że pokrywają najpopularniejsze wymagania dotyczące sposobu dostarczenia kodu. Dla ograniczenia liczby plików wszystkie zmienne zostały umieszczone w tym pliku.

<?xml version="1.0" encoding="iso-8859-2"?>
<project name="Project name" basedir="." default="all">
	<description>Tutorial ANT na http;//4programmers.net/java/Ant</description>

	<property file="build.properties" />
	<property name="target" value="./bin/" />
	<property name="target.classes" value="./bin/classes/" />
	<property name="target.test" value="./bin-test/" />
	<property name="target.jar" value="./bin/jar" />
	<property name="lib" value="./lib" />
	<property name="lib.junit" value="${lib}/junit4.jar" />
	<property name="reports.tests" value="./bin-test/raport/" />
	<property name="src" value="./src" />
	<property name="src.test" value="./test" />
	<property name="main.package" value="pl.koziolekweb.programmers.ant" />
	<property name="main.package.dir" value="pl/koziolekweb/programmers/ant" />

	<target name="clean" description="Usuwa katalogi ze skompilowanym kodem">
		<delete includeemptydirs="true" failonerror="no">
			<fileset dir="${target.classes}" includes="**/*" />
		</delete>
		<delete includeemptydirs="true" failonerror="no">
			<fileset dir="${target}" includes="**/*" />
		</delete>
		<delete includeemptydirs="true" failonerror="no">
			<fileset dir="${target.test}" includes="**/*" />
		</delete>
	</target>

	<target name="create" depends="clean" description="Tworzy strukturę projektu">
		<mkdir dir="${src}/${main.package.dir}" />
		<mkdir dir="${src.test}/${main.package.dir}" />
		<mkdir dir="${target.classes}" />
		<mkdir dir="${target.test}" />
		<mkdir dir="${target.jar}" />
		<mkdir dir="${lib}" />
		<mkdir dir="${reports.tests}" />
	</target>

	<target name="compile" depends="create" description="kompiluje kod">
		<javac srcdir="${src}" destdir="${target.classes}" />
	</target>

	<target name="test-compile" description="kompiluje kod" depends="compile">
		<javac srcdir="${src.test}" destdir="${target.test}" classpath="${lib.junit};${target.classes}" />
	</target>

	<target name="run-test" depends="test-compile" description="Uruchamia testy jednostkowe">
		<junit>
			<classpath>
				<pathelement location="${lib}" />
				<pathelement location="${lib.junit}" />
				<pathelement path="${target.classes}" />
				<pathelement path="${target.test}" />
			</classpath>
			<batchtest fork="yes" todir="${reports.tests}">
				<fileset dir="${src.test}">
					<include name="**/*Test.java" />
				</fileset>
			</batchtest>
			<formatter type="xml" />
		</junit>
	</target>

	<target name="package" depends="compile" description="tworzy plik jar">
		<jar destfile="${target.jar}/app.jar">
			<fileset dir="${target.classes}" />
		</jar>
	</target>

	<target name="test-package" depends="test-compile" description="tworzy plik jar z testami">
		<jar destfile="${target.jar}/app-test.jar">
			<fileset dir="${target.test}" />
		</jar>
	</target>

	<target name="src-package" description="tworzy plik jar ze źródłami i źródłami testów">
		<jar destfile="${target.jar}/app-src.jar">
			<fileset dir="${src}" />
		</jar>
		<jar destfile="${target.jar}/app-src-test.jar">
			<fileset dir="${src.test}" />
		</jar>
	</target>

	<target name="javadoc">
		<javadoc packagenames="pl.koziolekweb.programmers.ant*" sourcepath="${src}" defaultexcludes="yes" destdir="${target}/docs/api" author="true" version="true" use="true" windowtitle="App API" classpath="${target.jar}/app.jar" />
		<javadoc packagenames="pl.koziolekweb.programmers.ant*" sourcepath="${src.test}" defaultexcludes="yes" destdir="${target}/docs/test-api" author="true" version="true" use="true" windowtitle="App tests API" classpath="${lib.junit};${target.test}"/>
		<zip destfile="${target}/docs/api.zip" basedir="${target}/docs/api/" />
		<zip destfile="${target}/docs/test-api.zip" basedir="${target}/docs/test-api/" />
	</target>

	<target name="all" depends="package,test-package,src-package"></target>
</project>

Usuwanie starych plików

Pierwszym celem jest clean. Usuwa on wszystkie stare skompilowane pliki, pliki JAR i stare testy. Zadanie delete przyjmuje w tym przypadku listę plików do usunięcia z podanego katalogu – ale bez tego katalogu. W trakcie określania zbioru plików, fileset, istnieje możliwość stworzenia listy wyłączeń przez zdefiniowanie atrybuty albo elementu excludes.

Tworzenie katalogów

Ten cel został już omówiony wcześniej.

Kompilacja źródeł i testów

Te dwa cele – reprezentowane przez compile i test-compile – są najważniejszymi elementami projektu. Określają, które katalogi zawierają pliki źródłowe i pozwalają na ich kompilację. W przypadku compile proces jest bardzo prosty: za pomocą atrybutu srcdir określono katalog źródłowy, a za pomocą destdir docelowy. Kompilacja testów jest trochę bardziej skomplikowana, ponieważ wymaga określenia, poza katalogiem źródłowym i docelowym, także ścieżki z zależnościami. Można wykonać to na kilka sposobów. Najprostszym jest użycie atrybutu classpath i ręczne dodanie wszystkich potrzebnych elementów. Cel test-compile jest uzależniony od compile, ponieważ do uruchomienia testów potrzebne są skompilowane klasy aplikacji.

Uruchomienie testów

Ten cel jest trochę bardziej skomplikowany. Po pierwsze, element classpath jest określony za pomocą listy elementów pathelement. Pozwala to na budowanie długich ścieżek i na dłuższą metę jest znacznie bardziej wygodne. Po drugie, użyty został element batchtest zamiast test. Pozwala on na konfigurację testów na podstawie ścieżki, a test przyjmuje jako atrybut class pojedyncze klasy testowe.

Tworzenie pakietów

Cele package,test-package i src-package mają za zadanie utworzenie plików JAR zawierających odpowiednio: aplikację, testy, kod źródłowy aplikacji i kod źródłowy testów.

Tworzenie dokumentacji

Cel javadoc tworzy dokumentację kodu źródłowego i testów, a następnie pakuje ją do plików ZIP. Przy tworzeniu dokumentacji należy też zdefiniować classpath, w przeciwnym wypadku javadoc zwróci błędy podobne do tych, jakie zwraca kompilator, gdy nie odnajdzie zalezności.

Wszystko naraz

Ostatni cel jest domyślny. Uruchamia wszystkie poprzednie, dbając o to, by zależności były uruchamiane tylko raz.

Zobacz też

0 komentarzy