Java

Ant

  • 2009-11-16 14:27
  • 0 komentarzy
  • 5346 odsłon
  • Oceń ten tekst jako pierwszy
Spis treści

     1 Wstęp
     2 Struktura projektu
     3 Cele i zadania
          3.1 Target - cel
          3.2 Task - zadanie
          3.3 Przykładowe zadania wbudowane
     4 Parametryzowanie zadań
          4.1 Zmienne w pliku build.xml
          4.2 Użycie pliku build.properties
     5 Przykłady
          5.1 Usuwanie starych plików
          5.2 Tworzenie katalogów
          5.3 Kompilacja źródeł i testów
          5.4 Uruchomienie testów
          5.5 Tworzenie pakietów
          5.6 Tworzenie dokumentacji
          5.7 Wszystko naraz


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.
Strona domowa projektu: http://ant.apache.org

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 windowsie: %ANT_HOME%/bin). Następnie sprawdzamy czy wszystko jest prawidłowo skonfigurowane:
[email protected]:~$ 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 XML 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:
  1. name - nazwa projektu, która nie jest obowiązkowa.
  2. basedir - ścieżka bazowa od której są liczone wszystkie ścieżki względne. Też nieobowiązkowe, ale domyślnie jest to ścieżka do folderu z plikiem build.xml.
  3. default - domyślne zadanie, które będzie wykonane jeżeli nie podamy zadań do wykonania. Od wersji 1.6 biblioteki, każdy projekt posiada domyślnie skonfigurowane wszystkie zadania dostarczone razem z biblioteką.

Jak zatem ł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 konieczne, ale często trzeba się 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ą, ale można i samemu stworzyć zadanie poprzez rozszerzenie klasy Task.

Target - cel


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. Zależności definiujemy za pomocą atrybutu depends w którym poddajemy nazwy zależnych celi rozdzielone przecinkiem.
Dodajmy do naszego celu kilka zadań, które będą nam tworzyć strukturę projektu.

Task - zadanie


Zadanie jest pojedynczą operacją wykonywaną by osiągnąć cel. Może być to na przykład stworzenie katalogu, wywołanie kompilatora lub wypisanie czegoś w konsoli. Ilość zadań w ancie jest ogromna. Biblioteka posiada około 70 wbudowanych zadań, a istnieje jeszcze możliwość ściągnięcia dodatkowy bibliotek jak i stworzenia własnego rozwiązania.
Każde zadanie posiada unikalną listę parametrów, które są przekazywane jako atrybuty lub tagi 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 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 służące do kopiowania plików, delete usuwające pliki i zip pozwalające na pakowanie plików.

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, ścieżkach do bibliotek. warto by 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ą uzycie pliku build.properties. W obu przypadkach odwolanie do wartości zmienne 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 w zależności od np. środowiska lub osoby, która uruchamia proces to nie wskazane 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.

Użycie pliku 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 różnicy pomiędzy tymi metodami definiowania zmiennych. Plik build.properties może być już wyjęty z pod 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 pliku build.xml.

<?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 ono 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ść stworzeni 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 też ścieżki z zależnościami. Można wykonać to zadanie na kilka sposobów. Najprostszym jest użycie atrybutu classpath i ręczne dodanie wszystkich potrzebnych elementów. Cel test-compile jest uzależnione od compile ponieważ do uruchomienia testów potrzebne są skompilowane klasy aplikacji.

Uruchomienie testów


Ten cel jest trochę bardziej skomplikowany. Po pierwsze classpath jest określony za pomocą listy elementów pathelement. Pozwala to na budowanie długich ścieżek i na dłuższą, nomen omen, 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


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

Wszystko naraz


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