1 Apache ANT Narzędzie do automatyzacji zadań projektowych Dawid Weiss 004 006 Spis treści
1 Wprowadzenie ANT jest odpowiednikiem programu MAKE, znanego użytkownikom systemów Unix, i służy do automatyzacji wykonywania serii poleceń zmierzających do wykonania pewnego określonego celu. Celem tym, w przypadku zadań programistycznych, może być kompilacja plików źródłowych, usunięcie plików tymczasowych ale i również zupełnie inne zadania: opublikowanie dokumentacji na stronę WWW, wykonanie testów jednostkowych i generacja raportu w formie HTML, czy też wysłanie protokołem FTP plików na serwer dystrybucyjny. O sile narzędzia świadczy wachlarz możliwości jego użycia. Popularność ANTa jest wynikiem paru czynników: przenośność ANT jest napisany w Javie i używa języka XML do opisu skryptów automatyzacji. Poprawnie napisany skrypt będzie zatem działał identycznie zarówno w środowisku Windows, jak i innych (Linux czy MacOS). dużo gotowych zadań Biblioteka ANTa zawiera wiele gotowych zadań takich, jak kompilator Java, kompresja ZIP, czy też wszechstronne zadania do obsługi plików. integracja ANT jest łatwo integrowalny z innymi środowiskami programistycznymi. I tak Eclipse i wszystkie inne środowiska programisty umożliwiają uruchamianie skryptów bezpośrednio ze środowiska wizualnego. automatyzacja ANT powstał z myślą o automatyzacji zadań i daje się łatwo uruchamiać w sposób automatyczny (na przykład co pewien czas). Można ten fakt wykorzystać aby sprawdzać integralność projektu za pomocą testów regresji (określaną też czasem terminem continuous integration). rozszerzalność Czegokolwiek nie ma jeszcze w programie ANT, daje się łatwo zaprogramować jako własne zadania (w języku Java oczywiście). automatyzacja zadań Bez owijania w bawełnę: ANT jest używany zwykle do automatyzacji zadań programistycznych przez osoby związane z programowaniem w języku Java. Jednak nic nie stoi na przeszkodzie aby użyć go do każdego innego języka lub też zadania wymagającego powtarzalnych, identycznych kroków wykonania. Koncepcje podstawowe.1 Terminologia i jej znaczenie Przed przystąpieniem do programowania skryptów w programie ANT, warto przyswoić sobie terminologię towarzyszącą temu produktowi. Każdy skrypt ANTa to plik zapisany w języku XML (domyślnie ANT poszukuje pliku o nazwie build.xml, choć nie trzeba się do tej konwencji stosować). Ilustracja?? prezentuje główne bloki, z których składa się każdy skrypt: cele, zadania i powiązania. Ich krótkie omówienie znajduje się poniżej. cele Każdy skrypt składa się z zestawu nazwanych celów (ang. target). Każdy cel powiniem posiadać unikalną nazwę, która jednocześnie definiuje jego przeznaczenie. I tak typowymi celami będą: skrypty ANTa cele clean cel czyszczący projekt z plików tymczasowych, compile cel kompilujący pliki źródłowe, test cel uruchamiający testy jednostkowe, build cel kompilujący pliki źródłowe i wszelką inna dokumentację (przygotowanie wersji dystrybucyjnej). Cele są dokładniej omówione w rozdziale?? na stronie??. zadania Na każdy cel składają się liniowo wykonywane zadania (ang. task). Zadania owe są w większości predefiniowane i opisane w dokumentacji do ANTa. Ich stopień złożoności jest różny: od prostego kopiowania pliku (zadanie copy), do złożenia pełnej aplikacji internetowej (zadanie war). Przykłady dostępnych zadań poznamy w rozdziale?? na stronie??. zadania
RYSUNEK 1: Wizualizacja struktury skryptu ANTa. Plik skryptu, nazwany tutaj build.xml, zawiera nazwane cele (target), które z kolei zawierają podzadania (task). Cele są powiązane między sobą zależnościami (ang. dependency). powiązania Jeśli wykonanie jednego celu zależy od pomyślnego wykonania innego, to można zdefiniować powiązanie lub zależność (ang. dependency) między celami. Przykładowo, zadanie uruchamiające testy jednostkowe zależy od tego, czy udało się pomyślnie przeprowadzić proces kompilacji. Zadanie kompresji finalnego pliku ZIP z projektem może zależeć zarówno od poprawnej kompilacji, jak i (co wystarczy) od poprawnego wykonania testów. zależności Klarowne deklaracje zależności między celami są głównym atutem ANTa. Poprawnie zaprojektowany plik automatyzacji zawiera autonomiczne, drobne cele, które są powiązane ze sobą w większą całość właśnie przy pomocy powiązań. Wielokrotnie zdarza się, że niektóre cele nie posiadają wcale zadań, a jedynie sieć zależności łączącą inne cele w jeden bardziej skomplikowany. Programista ANTa nie musi przejmować się poprawną kolejnością wykonania celów zadanie sortowania ich w odpowiedniej kolejności (sortowanie topologiczne) przejmuje na siebie ANT.. Plik skryptu i uruchamianie programu ANT Plik projektu to plik zapisany w języku XML, który zawiera w sobie definicję dostępnych w projekcie celów. Zwykle dla każdego projektu mamy jeden plik automatyzujący zadania z nim związane. Oczywiście w razie konieczności plików takowych można mieć więcej niż jeden (co komplikuje jednak nieco wywołanie programu ant, bowiem należy wtedy wskazać, który plik ma być analizowany). Każdy plik projektu musi obowiązkowo zawierać cel domyślny (wywoływany, gdy nie poda się żadnego argumentu do polecenia ant. Trywialnym skryptem może więc być znakomity przykład hello world : 1 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <project name="my project" default="hello"> 4 <target name="hello" description="says hello"> 5 <echo>hello world.</echo> 6 </target> 7 </project>
4 Należy zwrócić uwagę na atrybut default, który wskazuje domyślny cel. Uruchomienie tego skryptu 1 daje następujący wynik: 1 g:\projects>ant -f empty.xml Buildfile: empty.xml 4 hello: 5 [echo] Hello world. 6 7 BUILD SUCCESSFUL 8 Total time: 1 second Opcja -f służy do wskazania pliku skryptu o nazwie innej niż build.xml (w przypadku której może zostać pominięta). Jak widać z powyższego listingu, wynikiem działania programu jest informacja o celach i zadaniach, które zostały wykonane. Argumentami programu ANT są cele, które należy wykonać. Równie dobrze możemy więc wpisać: Uruchamianie specyficznych celów 1 g:\projects>ant -q -f empty.xml hello [echo] Hello world. 4 BUILD SUCCESSFUL 5 Total time: 0 seconds Tym razem program ANT uruchomiono z opcją -q, która powoduje mniejszą liczbę wypisywanych komunikatów (ang. quiet) oraz ze specyficznym celem hello (w naszym przypadku identycznym z domyślnym). Aby dowiedzieć się, jakie cele zawiera plik ANTa, można przekazać do programu opcję -p: 1 g:\projects>ant -f empty.xml -p Buildfile: empty.xml Informacja o dostępnych celach 4 Main targets: 5 6 hello Says hello 7 Default target: hello Jak widać, jedyny cel (domyślny) tego pliku to hello.. Cele Jak już wspomnieliśmy, cele to główne bloki budujące plik skryptu ANTa. Każdy cel zawiera atrybut name, który jest jednoznacznym identyfikatorem tego celu. Dodatkowo można w definicji celu umieścić atrybut description, który pojawi się jako opis tego celu w momencie wywołania ANTa z opcją -p. Konwencja nazw celów jest oczywiście jedynie umową. Dla projektu łatwo jest wypisać wszystkie zdefiniowane w nim cele (wraz z dokumentacją), więc nawet zarzucenie tej konwencji nie czyni dużej szkody. Doświadczony programista będzie jednak oczekiwał celów o określonych nazwach (takich jak clean czy build). Plik ANTa definiujący dwa cele: hello i world może wyglądać następująco: 1 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <project name="hello-1" default="hello"> 4 <target name="hello" description="says hello"> 5 <echo>hello</echo> 6 </target> 7 8 <target name="world" description="says world"> 9 <echo>world.</echo> 10 </target> 11 </project> 1 Instalacja programu ANT jest omówiona w punkcie?? na stronie??
5 Przykładowe zaś wykonanie powyższego skryptu wygląda następująco (proszę zwrócić uwagę na podane jawnie oba cele w argumentach do programu): 1 g:\projects>ant -f hello-1.xml world hello Buildfile: hello-1.xml 4 world: 5 [echo] World. 6 7 hello: 8 [echo] Hello 9 10 BUILD SUCCESSFUL 11 Total time: 0 seconds.4 Powiązania Zależność jednego celu od drugiego definiujemy przy pomocy atrybutu depends w elemencie target. Wartością atrybutu są, oddzielone przecinkami, nazwy celów, które mają się wykonać przed zadanym. Przykładowo, nasz poprzedni skrypt definiujący cele hello i world może wyglądać następująco: 1 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <project name="hello-" default="hello"> 4 <target name="hello" description="says hello"> 5 <echo>hello</echo> 6 </target> 7 8 <target name="world" description="says world" depends="hello"> 9 <echo>world.</echo> 10 </target> 11 </project> Wówczas wywołanie celu world będzie zawsze poprzedzone wykonaniem celu hello: 1 g:\projects>ant -f hello-.xml world Buildfile: hello-.xml 4 hello: 5 [echo] Hello 6 7 world: 8 [echo] World. 9 10 BUILD SUCCESSFUL 11 Total time: 0 seconds.5 Zadania Zadania w skrypcie ANTa są deklarowane jako elementy języka XML wewnątrz celu. Zadania mniej skomplikowane są zwykle deklarowane jako elementy puste (jedynie z atrybutami), lub zawierające pewne instrukcje wewnątrz deklaracji. Przykładowo, w poniższym celu tworzony jest katalog i wyświetlana jest informacja dla użytkownika: 1 <target name="create.tmp"> <mkdir dir="tmp" /> <echo>folder "tmp" created.</echo> 4 </target> Nietrudno jest zgadnąć, że ANT wykonuje zadania w kolejności ich deklaracji w skrypcie. Ten i inne ważne elementy sposobu wykonania przez ANT skryptu automatyzacji podsumowano w punktach poniżej: Sposób wykonania skryptu
6 1. Zadania są wykonywane sekwencyjnie: w kolejności zależności między celami, a następnie w obrębie jednego celu.. W ogólnym przypadku cały skrypt będzie wykonywany dokładnie przez jeden wątek. Zrównoleglenie zadań jest możliwe przy pomocy zadania o nazwie parallel, jednak w tym tekście nie będziemy poszerzali tego zagadnienia.. Wykonanie skryptu jest przerywane i kończy się błędem w momencie, gdy którekolwiek zadanie zakończy się błędem (pozostałe zadania nie są wykonywane). W przypadku niektórych zadań można w sposób jawny żądać ignorowania błędów wykonania. Powyższe zasady ilustruje następujący przykład: 1 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <project name="failure" default="build"> 4 <target name="build"> 5 <copy file="from.txt" tofile="to.txt" /> 6 <echo>file copied.</echo> 7 </target> 8 </project> Wykonanie tego skryptu (kopiowanie pliku, który nie istnieje) kończy się błędem. Proszę zwrócić uwagę na to, że zadanie echo nie wykonało się: 1 >ant -f failure.xml Buildfile: failure.xml 4 build: 5 6 BUILD FAILED 7 D:\failure.xml:5: Warning: Could not find file D:\from.txt to copy. 8 9 Total time: 1 second Można jednak polecić zadaniu copy ignorowanie błędów: 1 <?xml version="1.0" encoding="utf-8" standalone="yes"?> <project name="failure" default="build"> 4 <target name="build"> 5 <copy file="from.txt" tofile="to.txt" failonerror="false" /> 6 <echo>file copied.</echo> 7 </target> 8 </project> wtedy cały skrypt kończy się sukcesem: 1 >ant -f failure-.xml Buildfile: failure-.xml 4 build: 5 [copy] Warning: Could not find file D:\from.txt to copy. 6 [echo] File copied. 7 8 BUILD SUCCESSFUL 9 Total time: 0 seconds Zadania o większym stopniu skomplikowania mogą składać się z paru zagnieżdżonych instrukcji języka XML (w tym również deklaracji zbiorów plików, omówionych w rozdziale??). Przykład takiego bardziej skomplikowanego zadania (detale tutaj pomijamy) znajduje się poniżej: 1 <junit dir="${build.dir}/jni-testdir" fork="true" haltonerror="true" haltonfailure="true" printsummary="false">
7 <env key="ld_library_path" file="${build.dir}/jni-testdir" /> 4 5 <classpath> 6 <pathelement path="${build.dir}/classes" /> 7 <pathelement path="${build.dir}/test" /> 8 <path refid="libs" /> 9 </classpath> 10 11 <formatter type="plain" usefile="false" /> 1 1 <sysproperty key="java.library.path" path="${build.dir}/jni-testdir" /> 14 15 <batchtest> 16 <fileset dir="${build.dir}/test"> 17 <include name="**/*test.class" /> 18 </fileset> 19 </batchtest> 0 </junit> Powstaje oczywiście pytanie skąd czerpać wiedzę na temat dostepnych zadań? W większości zastosowań w zupełności wystarczał będzie zestaw podstawowych zadań dostarczany z ANTem (tak zwanych core tasks). Ich świetny opis i zestawienie oferuje dokumentacja ANTa, dostępna na stronie http://ant.apache.org/manual (należy wybrać z menu opcję Ant Tasks a następnie Core Tasks). Inna użyteczna biblioteka zadań to ANT-CONTRIB, dostępna pod adresem: http: //ant-contrib.sourceforge.net/. Tam, gdzie zadania podstawowe lub ich kompozycja nie wystarcza, można zawsze pokusić się o napisanie własnego zadania w języku Java lub w formie skryptu. Tematykę tę porusza rozdział??..6 Deklarowanie zbiorów plików i katalogów Automatyzacja budowy projektu to zwykle manipulacje pewnymi zbiorami plików. Wiele zadań (przykładowo copy, delete lub javac) umożliwia deklarowanie zbiorów plików w postaci elementu fileset. Pojedynczy element fileset zawsze wskazuje na jakiś katalog w systemie plików. Domyślnie jego wartością są wszystkie pliki i katalogi zawarte we wskazanym katalogu. Zbiór ten może być zawężony poprzez użycie klauzul włączenia (include) lub wyłączenia (exclude) ścieżek plików pasujących do pewnych wzorców. Przykładowo, wszystkie pliki z rozszerzeniem java w katalogu src można skopiować używając następującej deklaracji: 1 <copy todir="src-copy"> <fileset dir="src"> <include name="**/*.java" /> 4 </fileset> 5 </copy> Jak widać wzorce w elementach include i exclude są specyficzne dla ANTa (nie są to wyrażenia regularne). Symbole specjalne, które mogą pojawić się we wzorcu to: SYMBOL OPIS * Symbol zastępujący dowolny ciąg znaków w nazwie pliku lub katalogu. ** Symbol oznaczający dowolny ciąg podkatalogów.? Symbol zastępujący dowolną literę w nazwie pliku lub katalogu. Sprawne posługiwanie się wzorcami wymaga pewnej praktyki. Należy poświęcić jej choć trochę czasu. Jakie pliki wyznacza ten fileset? 1 <fileset dir="."> <include name="**/local/**/*test.java" /> <exclude name="**/controller*.java" /> 4 </fileset>
8.7 Deklarowanie i użycie ścieżek Ten rozdział wymaga jeszcze ukończenia. Proszę słuchać na zajęciach! Więcej informacji znajduje się również pod adresem: http://ant.apache.org/manual/using. html#path..8 Deklarowanie i użycie stałych w projekcie Ten rozdział wymaga jeszcze ukończenia. Proszę słuchać na zajęciach! Więcej informacji znajduje się również pod adresem: http://ant.apache.org/manual/using. html#properties Programowanie własnych zadań Programowanie własnych zadań w programie ANT jest bardzo, bardzo łatwe i daje wiele satysfakcji (i możliwości, oczywiście). Zaprogramowanie nowego zadania sprowadza się do napisania klasy, która deklaruje metodę o sygnaturze public void execute(). Ten rozdział wymaga jeszcze ukończenia. Proszę słuchać na zajęciach! Więcej informacji znajduje się również w rozdziale Developing with Ant w dokumentacji ANTa pod adresem: http://ant.apache.org/manual/. Poprawne zadania powinny dziedziczyć z klasy org.apache.tools.ant.task, jednak nie jest to wymagane.
9 4 Ćwiczenia praktyczne 4.1 Podstawy 4.1.1 Skrypt tworzący katalogi tymczasowe i kasujący je. 1 <project name="cw1" basedir="." default="init"> <target name="init"> 4 <mkdir dir="tmp" /> 5 <mkdir dir="tmp/build" /> 6 </target> 7 8 <target name="clean"> 9 <delete dir="tmp" failonerror="true" 10 includeemptydirs="true" quiet="true" /> 11 </target> 1 1 </project> 4.1. Skrypt kompilujący źródła, wprowadzenie stałych 1 <project name="cw" basedir="." default="compile"> <property name="build.dir" location="tmp/build" /> 4 5 <target name="init"> 6 <mkdir dir="${build.dir}" /> 7 </target> 8 9 <target name="clean" description="clean up temporary files"> 10 <delete dir="${build.dir}" failonerror="true" 11 includeemptydirs="true" quiet="true" /> 1 </target> 1 14 <target name="compile" depends="init" description="compiles sources"> 15 <javac destdir="${build.dir}" 16 optimize="false" debug="true" deprecation="true" 17 failonerror="true" 18 includeantruntime="false" includejavaruntime="false"> 19 <src location="src" /> 0 </javac> 1 </target> </project> 4.1. Skrypt kompilujący źródła, z ustawieniem specyficznej ścieżki klas (CLASSPATH) 1 <project name="cw" basedir="." default="compile"> <property name="build.dir" location="tmp/build" /> 4 5 <target name="init"> 6 <mkdir dir="${build.dir}" /> 7 </target> 8 9 <target name="clean" description="clean up temporary files"> 10 <delete dir="${build.dir}" failonerror="true" 11 includeemptydirs="true" quiet="true" /> 1 </target> 1 14 <target name="compile" depends="init" description="compiles sources"> 15 <javac destdir="${build.dir}" 16 optimize="false" debug="true" deprecation="true" 17 failonerror="true" 18 includeantruntime="false" includejavaruntime="false"> 19 <src location="src-cw" /> 0 <classpath>
10 1 <fileset dir="lib" includes="**/*.jar" /> </classpath> </javac> 4 </target> 5 6 </project> 4.1.4 Skrypt tworzący plik JAR 1 <project name="cw4" basedir="." default="jar"> <property name="build.dir" location="tmp/build" /> 4 <property name="dist.dir" location="tmp/dist" /> 5 6 <target name="init"> 7 <mkdir dir="${build.dir}" /> 8 <mkdir dir="${dist.dir}" /> 9 </target> 10 11 <target name="clean" description="clean up temporary files"> 1 <delete dir="${build.dir}" failonerror="true" 1 includeemptydirs="true" quiet="true" /> 14 </target> 15 16 <target name="compile" depends="init" description="compiles sources"> 17 <javac destdir="${build.dir}" 18 optimize="false" debug="true" deprecation="true" 19 failonerror="true" 0 includeantruntime="false" includejavaruntime="false"> 1 <src location="src-cw" /> <classpath> <fileset dir="lib" includes="**/*.jar" /> 4 </classpath> 5 </javac> 6 </target> 7 8 <target name="jar" depends="compile"> 9 <jar compress="true" jarfile="${dist.dir}/test.jar" update="false"> 0 <fileset dir="${build.dir}" /> 1 </jar> </target> 4 </project> 4. Skrypty bardziej zaawansowane 4..1 Testowanie kodu z ANTa przy pomocy JUnita 1 <project name="cw5" basedir="." default="test"> <property name="build.dir" location="tmp/build" /> 4 5 <target name="init"> 6 <mkdir dir="${build.dir}" /> 7 </target> 8 9 <target name="clean" description="clean up temporary files"> 10 <delete dir="${build.dir}" failonerror="true" 11 includeemptydirs="true" quiet="true" /> 1 </target> 1 14 <target name="compile" depends="init" description="compiles sources"> 15 <javac destdir="${build.dir}" 16 optimize="false" debug="true" deprecation="true" 17 failonerror="true" 18 includeantruntime="false" includejavaruntime="false"> 19 <src location="src-cw" /> 0 <classpath> 1 <fileset dir="lib" includes="**/*.jar" />
11 </classpath> </javac> 4 </target> 5 6 <target name="test" depends="compile" description="runs test cases"> 7 <junit dir="${build.dir}" fork="true" 8 errorproperty="junit.error" failureproperty="junit.failure" 9 haltonerror="true" haltonfailure="true" 0 printsummary="true"> 1 <classpath> <pathelement location="${build.dir}" /> 4 </classpath> 5 6 <batchtest> 7 <fileset dir="${build.dir}"> 8 <include name="**/*test.class" /> 9 </fileset> 40 </batchtest> 41 </junit> 4 </target> 4 </project> 4. Programowanie własnych zadań 4..1 Skrypt z własnym zadaniem: dodaj Skrypt ANTa, który najpierw kompiluje kod zadania, a następnie je wywołuje: 1 <project name="cw8" basedir="." default="dodaj"> <property name="build.dir" location="tmp/build" /> 4 <target name="compile"> 5 <mkdir dir="${build.dir}" /> 6 <javac destdir="${build.dir}" 7 optimize="false" debug="true" deprecation="true" 8 failonerror="true" 9 includeantruntime="true"> 10 <src location="src-cw8" /> 11 <classpath> 1 <fileset dir="lib" includes="**/*.jar" /> 1 </classpath> 14 </javac> 15 </target> 16 17 <target name="dodaj" depends="compile"> 18 <taskdef name="dodaj" classname="pakiet.dodaj"> 19 <classpath location="${build.dir}" /> 0 </taskdef> 1 <dodaj a="5" b="6" wynik="stala.wynik" /> <echo message="wynik: ${stala.wynik}" /> </target> 4 </project> Sam kod zadania Dodaj w Javie: 1 package pakiet; import org.apache.tools.ant.*; 4 5 public class Dodaj { 6 private int a; 7 private int b; 8 private String name; 9 private Project project; 10 11 public void seta(int a) { 1 this.a = a; 1 } 14
1 15 public void setb(int b) { 16 this.b = b; 17 } 18 19 public void setwynik(string name) { 0 this.name = name; 1 } public void setproject(project project) 4 { 5 this.project = project; 6 } 7 8 public void execute() { 9 project.setproperty(name, Integer.toString(a + b)); 0 } 1 }
1 A Instalacja ANTa Instalacja programu ANT polega na wykonaniu następujących czynności: 1. Pobraniu pliku ZIP z programem ze strony http://ant.apache.org.. Odkompresowaniu pliku ZIP do katalogu lokalnego. Proszę zwrócić uwagę by w ścieżce prowadzącej do instalacji nie było znaków spacji ani innych specjalnych. Dobrym kandydatem jest, na przykład, c:\java\ant.. Ustawieniu zmiennej systemowej ANT_HOME tak, aby wskazywała na katalog instalacyjny. 4. Dodaniu katalogu ANT_HOME/bin do ścieżki domyślnej (zmiennej środowiskowej PATH). Program ANT jest gotowy do pracy gdy wywołanie ant powoduje wypisanie komunikatu o braku pliku build.xml. Niektóre zadania A N Ta wymagają specyficznych kroków instalacji. Przykładowo, zadanie kompilacji plików Java (javac) wymaga zdefiniowania zmiennej środowiskowej JAVA_HOME, wskazującej na katalog instalacyjny JDK (Java Development Kit). B Inne materiały Program ANT posiada bardzo dobrą dokumentację użytkownika (niestety jedynie w języku angielskim), która dołączana jest do programu (katalog docs) oraz jest dostępna on-line pod adresem: http://ant.apache.org/manual/index.html.