08 lutego 2006

Zarządzanie projektem za pomocą Apache Maven 2

Zazwyczaj wiele czynności jakie programista wykonuje w projekcie odbywa się z poziomu wybranego środowiska programistycznego (ang. IDE = Integrated Development Environment). W nim tworzy strukturę katalogów projektu korzystając z dostępnych wizardów. Dalsze kroki związane z tworzeniem aplikacji: budowanie (kompilacja oraz przeniesienie wymaganych plików do odpowiednich katalogów), testowanie (wykonywanie testów jednostkowych), zatwierdzanie/pobieranie zmian do/z systemu kontroli wersji, generowanie dokumentacji (javadoc) - odbywają się z poziomu IDE, jako przyciski na pasku zadań. To, co nie jest dostępne w środowisku programistycznym IDE, zazwyczaj nie jest wykorzystywane w projekcie. Dodatkowo, skoro czynności są wykonywane z poziomu IDE i są od niego zależne, wymusza to na zespole stosowanie identycznego środowiska oraz struktura projektu nie jest wynikiem decyzji zespołu, ale podyktowane jest wymogami środowiska programistycznego. Wszystko jest do zaakceptowania, aż do momentu, kiedy zdecydujemy się wykonywać część zadań bez uruchamiania IDE, np. okresowe wykonywanie testów jednostkowych bądź budowanie wersji instalacyjnych naszego projektu. Kolejną uciążliwością może być sprawne wykonywanie testów w środowisku serwera aplikacyjnego Java EE. Bezsprzecznie, w pewnym momencie będziemy potrzebować narzędzia do wykonywania zadań projektowych za nas, które umożliwi nam uniezależnienie się od wymagań IDE. Wielu w takim momencie sięgnie po rozwiązania specyficzne dla środowiska systemu operacyjnego, na którym pracuje, np. skrypty powłoki, ale jak wiadomo ich utrzymywanie nie jest przyjemne, szczególnie jeśli ilość zadań do wykonania przy ich pomocy wymusza zatrudnienie kolejnej osoby do ich utrzymania.

Bardziej zaawansowanym narzędziem pozwalającym na automatyzację zadań jest darmowy projekt Apache Ant. Jego wadą jest jednak konieczność tworzenia skryptów, nawet dla tak podstawowych zadań jak kompilacja, wykonanie testów jednostkowych, generowanie javadoc czy po prostu tworzenie wersji dystrybucyjnej. Celem Anta było uniezależnienie zespołów od korzystania ze specyficznego dla Uniksa narzędzia make, jak i rozbudowywanie narzędzia o nowe funkcjonalności (zadania) w Javie. Cel z pewnością został osiągnięty. Ant szybko okazał się najbardziej popularnym narzędziem wykorzystywanym przez programistów, a ilość zadań Ant rosła z każdym wydaniem. Niestety, Ant, oprócz zalet ma i swoje wady. Najbardziej doskwierającą i bardzo czasochłonną jest konieczność tworzenia skryptów, nawet dla bardzo podstawowych czynności projektowych. Komu chce się powtarzać te same kroki w kolejnych projektach podczas ich konfiguracji, jeśli część z nich już wykonano wcześniej, w innym projekcie? Brakowało narzędzia, którego głównym celem byłoby spojrzenie na wykonywane przez programistów zadania jako części projektu wraz z odziedziczeniem możliwości Anta. Jego następca musiał być równie prosty w obsłudze jak on sam i udostępniać możliwość uruchamiania już napisanych skryptów Ant. Dałoby to możliwość łagodnego przejścia użytkownikom Anta do nowego narzędzia. Dodatkowo, cechą wiodącą nowego narzędzia musiało być nie tylko utrzymywanie więzi z prekursorem pomysłu - Antem - ale również nowatorskie rozwiązanie w zakresie zarządzania zależnościami projektu - bibliotekami. Zarządzanie nimi wymaga dodatkowej pracy, której wykonanie dobrze byłoby, abyśmy mogli zlecić nowemu narzędziu.

Wszystkie wymienione cechy są dostępne w wolnym oprogramowaniu Apache Maven, który w porównaniu z Antem wprowadza swoistą rewolucję w zarządzaniu zadaniami projektowymi, a linia rozwojowa 2.x (z ostatnią wersją 2.0.2) jest bezsprzecznie liderem w tej kategorii. Wymienione cechy są jedynie namiastką możliwości Maven 2. Ich realizacja znacząco wpływa na obniżenie kosztów wytwarzania oprogramowania i skrócenie czasu realizacji projektu do niezbędnego maksimum przy zauważalnym podniesieniu jakości przez dostarczenie gotowych do wykonania celi (ang. goal). W wielu aktualnie prowadzonych projektach brak użycia Mavena wymusza manualne napisanie odpowiednich skryptów do wykonania raportów z przeprowadzenia testów, pokrycia przez nie kodu źródłowego, czy generowaniu rozmaitych dokumentacji projektowych, jak javadoc czy strona domowa projektu. Wszystkie te cechy powodują, że przy niezauważalnym nakładzie pracy podczas wdrożenia Maven jakość projektu zauważalnie wzrasta, a czas i koszty jego realizacji obniżają się.

Przez swoją prostotę w użyciu i dostępność wielu wtyczek wytwarzanie oprogramowania z jego pomocą nie kończy się na bardzo podstawowych czynnościach programistycznych jak kompilacja, czy wykonywanie testów, ale zachęca do użycia innych wtyczek, które automatycznie, bezkosztowo podnoszą jakość i zmniejszają koszt rozwoju oprogramowania. W innym przypadku wdrożenie ich wymagałoby dodatkowej wiedzy i czasu, co wielu z nas wie, że są najbardziej cennymi zasobami projektowymi i ciągle doskwiera ich brak.

W zasadzie nie ma żadnych wstępnych wymagań do korzystania z Mavena, poza wirtualną maszyną Java. Nie ma znaczenia, czy wzdrażamy go w projekcie w zaawansowanym stanie, czy przy jego pomocy tworzymy nowy. Rozpoczynamy od instalacji Mavena (co jest zwyczajnym rozpakowaniem paczki instalacyjnej do wybranego katalogu) i możemy rozpocząć pracę z jego pomocą. Sprawdzamy poprawność instalacji wykonując polecenie mvn z linii poleceń.
$ mvn --version
Maven version: 2.0.2
Wszystko co wykonujemy w Maven jest wynikiem uruchomienia wtyczki (ang. plugin). W skrócie, Maven jest systemem uruchamiania wtyczek, które mogą być napisane w językach skryptowych (Apache Jelly, BeanShell) lub Javie. Wtyczki mogą być zależne od innych wtyczek i wykonanie zadania będzie pociągało inne, jednakże podstawowe uruchamianie zadań zazwyczaj nie implikuje dodatkowych prac z naszej strony - kwestia definiowania zależności jest zazwyczaj po stronie autora wtyczki. Jest to bardzo podobne do zależności między zadaniami w Ant, w którym bardziej zaawansowany skrypt obarczony był dużym nakładem monotonnej pracy.

Istotną kwestią podczas pierwszego uruchamiania Mavena jest dostępność połączenia do centralnego repozytorium Maven. Jest to repozytorium artefaktów - bibliotek, wtyczek, wersji instalacyjnych, modułów J2EE i w sytuacji braku jednego podczas uruchomienia wtyczki, Maven automatycznie pobiera brakującą część z repozytorium. Upewnij się, że pracujesz podłączony do Internetu zanim rozpoczniesz jakiekolwiek przykłady z Maven (jeśli jesteś za ścianą ogniową - ang. firewall - uruchom Mavena wcześniej zdefiniowawszy MAVEN_OPTS="-Dhttp.proxyHost=numerIpProxy -Dhttp.proxyPort=numerPortu").

Pierwszą rzeczą z jaką każdy z nas się spotka podczas konfiguracji projektu jest określenie jego struktury katalogowej. Maven dostarcza wtyczkę archetype, której jednym z celi jest create. Wykonanie następującego polecenia stworzy predefiniowaną strukturę katalogów, która jest wystarczająca w większości z konfiguracji. Jeśli wdrażamy Maven do istniejącego projektu, istnieje możliwość konfiguracji Maven, aby respektował jego strukturę.
mvn archetype:create -DgroupId=pl.org.laskowski -DartifactId=aplikacja
Uruchomienie tego polecenia stworzy szkielet struktury katalogów na podstawie danych z pom.xml, z katalogiem głównym wyznaczonym przez artifactId (w naszym przypadku aplikacja) i podkatalogami src/main/java, który zawiera klasy projektu oraz src/test/java z klasami testów jednostkowych (klasy należą do pakietu pl.org.laskowski jak wskazano w groupId). Istotnym elementem tworzonym podczas wykonania powyższego polecenia jest plik pom.xml. POM to skrót od angielskiego Project Object Model, czyli model opisujący projekt. Jest to plik w formacie XML, w którym definiujemy wszystkie elementy związane z projektem, w tym docelową nazwę pliku wynikowego (najczęściej jar), który będzie tworzony w projekcie oraz zależności - biblioteki, które posłużą do budowania projektu (kompilacji klas). Konfiguracja projektu zarządzanego przez Maven to najczęściej modyfikacja jego pom.xml. Podczas dystrybucji projektu istnieje możliwość opublikowania pom.xml do repozytorium tak, że każdy projekt oparty o niego nie potrzebuje definiować dalszych zależności wyznaczanych przez zależny projekt.

Maven 2 wprowadził bardzo istotną funkcjonalność, która była niedostępna w poprzednich wersjach - przechodność zależności (ang. transitive dependencies). Jej zalety można dostrzec podczas konfiguracji zależności naszego projektu. Ile to razy borykamy się z rozwiązywaniem problemów z kompilacją, kiedy chcemy skorzystać z pewnej biblioteki. Jeśli dołączana biblioteka zależy od innej, wtedy musimy tę zależność dołączyć również do naszego projektu. Każdy kto pracował z Ant wie ile trudów przysparza definiowanie CLASSPATH i później jego utrzymywanie. Dodatkowo należy zadbać o dostępność samych plików. Jest to bardzo czasochłonne zadanie, a udostępnienie przechodności zależności w Maven uwalnia nas od niego raz na zawsze. W sytuacji, kiedy zdecydujemy się na wdrożenie biblioteki X, a ona zależy od kolejnych bibliotek, nasz pom.xml zadeklaruje jedynie zależność od biblioteki X, a pozostałe zależności będą pobrane automatycznie przez Maven na podstawie pom.xml projektu, w którym powstała biblioteka X. Dodatkową cechą nowej wersji Maven związaną z zarządzaniem zależnościami jest jest możliwość definiowania zakresu wersji zależności projektu (ang. version ranges). Możemy w ten sposób wyrazić zakres wersji zależności, która jest wymagana w projekcie. W naszym przykładowym projekcie pom.xml deklaruje zależność od biblioteki JUnit w wersji 3.8.1.

Jak już wspomniałem Maven cechuje się różnorodnością wtyczek, których dostarczona konfiguracja wstępna jest wystarczająca w początkowej fazie projektu. Brak konieczności konfiguracji Maven pozwala na wdrożenie go nawet w niedojrzałym zespole programistów oszczędzając im trudów początkowej integracji z narzędziem, jednocześnie oszczędzając czas na konfigurację narzędzi pomocniczych, które dostarczane są w Maven (m.in. checkstyle, jdepend, xdoclet).

Po stworzeniu struktury katalogów, otwórzmy projekt w wybranym zintegrowanym środowisku programistycznym (IDE). W zależności od wyboru Maven dostarcza wtyczki do generowania deskryptorów projektu dla Eclipse i IntelliJ IDEA. NetBeans IDE 5.0 posiada wsparcie dla projektów opartych o Maven2 poprzez moduły projektu Mevenide for NetBeans. Zainstalowawszy moduły mamy możliwość uruchamiania wtyczek Maven2 z poziomu NetBeans IDE i nie jest wymagane generowanie plików konfiguracyjnych projektu. Wygenerowanie deskryptora dla Eclipse IDE to uruchomienie polecenia (ważne jest, aby wykonywać wszystkie następne polecenia w katalogu aplikacja, który został stworzony wcześniej podczas uruchomienia mvn archetype:create):
mvn -Declipse.workspace=<ścieżka-do-przestrzeni-roboczej-Eclipse> eclipse:add-maven-repo
gdzie ścieżka-do-przestrzeni-roboczej-Eclipse to np. c:\eclipse\workspace. W ten sposób definiujemy zmienną M2_REPO w Eclipse, która będzie wskazywała na katalog repozytorium lokalnego Maven. Kolejnym krokiem jest uruchomienie polecenia mvn eclipse:eclipse.
mvn eclipse:eclipse
Po wykonaniu polecenia, które powinno zakończyć się BUILD SUCCESSFUL możemy otworzyć projekt w Eclipse (Import > Existing Projects into Workspace). Nasz projekt zawiera dwa katalogi ze źródłami (src/main/java oraz src/test/java) oraz jedną bibliotekę składowaną w katalogu M2_REPO.

Katalog M2_REPO, lub innymi słowy lokalne repozytorium Maven, to kopia struktury repozytorium Maven z bibliotekami (zależnościami), które były pobrane podczas uruchamiania Maven. Standardowo, katalog z lokalnym repozytorium Maven znajduje się w katalogu domowym użytkownika (~/.m2/repository na Uniksie i %USERPROFILE%/.m2/repository na MS Windows). Podkatalogi są ułożone w ten sposób, że najpierw są katalogi, które składają się na groupId z pom.xml uruchamianych projektów z plikami, których nazwy odpowiadają artifactId i packaging w pom.xml. Tworzenie kopii repozytorium zdalnego Maven umożliwia pracę bez podłączenia z centralnym repozytorium Maven (tj. http://repo1.maven.org/maven2, czyli http://www.ibiblio.org/maven2/). Każde wykonanie polecenia mvn z opcją -o bądź --offline wykonywane jest bez próby pobrania zależności z repozytorium i brak jednego powoduje zatrzymanie wykonania Maven.

Po utworzeniu klas, które tworzą projekt czas na ich kompilację. Jakkolwiek IDE wykonują tę czynność wspaniale, to wykonanie tego polecenia pozwoli na zbudowanie projektu automatycznie, bez konieczności uruchamiania dodatkowego oprogramowania.
mvn compile
Pomyślne zakończenie polecenia to wyświetlenie komunikatu BUILD SUCCESSFUL. Wcześniejsze komunikaty informują o postępie prac i pomagają zdiagnozować problem, jeśli wystąpi. Bardzo pomocną opcją podczas uruchamiania Maven, który zakończył się błędem, jest opcja -e lub --errors, który dostarcza dodatkowych informacji do analizy przyczyny błędnego uruchomienia wtyczki.

Standardowy katalog ze skompilowanymi klasami to target/classes. W ogólności katalog target jest docelowym katalogiem wykorzystywanym przez wtyczki do zapisywania plików wynikowych swojej pracy. W przypadku wtyczki compile będą to klasy, jednakże uruchomienie innych wtyczek dostarczy innych artefaktów projektowych, np. raportów. Uruchommy polecenie mvn clover bądź mvn checkstyle, aby zobaczyć wynik ich działania - raporty jakościowe dotyczące pokrycia klas projektu przez testy jednostkowe w pierwszym przypadku i poprawności stylu kodu w drugim. Oba raporty są uruchamiane i generowane bez dodatkowej, w innym przypadku bardziej absorbującej, pracy. Zanim jednak wykonamy obie wtyczki ważna uwaga na temat pracy wtyczek i dostarczania przez nich celi.

Każda wtyczka ma swoją nazwę i jest zbiorem celi. Jeden z celi może być celem domyślnym, a więc wykonywanym, kiedy uruchomimy Maven z nazwą wtyczki. Część wtyczek nie definiuje domyślnego celu, więc konieczne jest wywołanie wtyczki z nazwą wybranego przez nas celu. Konfiguracja wtyczki odbywa się przez zmienne projektu, które definiuje się albo podczas uruchamiania, na linii poleceń (opcja -D), albo w pliku settings.xml. W przypadku wtyczki checkstyle mamy do dyspozycji dwa cele: checkstyle oraz check. Wykonanie jednego z nich to uruchomienie wtyczki po nazwie której występuje nazwa celu odseparowanego dwukropkiem, np. checkstyle:checkstyle lub checkstyle:check. Podobnie jest w przypadku wtyczki clover, gdzie dostępnych mamy, aż 4 cele: check, instrument, log i clover. Najczęściej jeden z dostępnych celi jest użyteczny podczas, gdy pozostałe są pomocnicze dla działania wtyczki i ich uruchomienie nie przyniesie wymiernych korzyści. Dostępne cele poznamy analizując dokumentacje wtyczki. Lista części dostępnych wtyczek w standardowej dystrybucji Maven znajduje się na http://maven.apache.org/plugins/index.html.

W celu uruchomienia raportów o jakości naszego projektu wykonajmy polecenia mvn checkstyle:checkstyle oraz mvn clover:clover. Podczas ich uruchomienia można zauważyć pobieranie dodatkowych zależności, które zapisywane są w naszym lokalnym repozytorium tak, że kolejne uruchomienie nie będzie wymagało pobrania ich ponownie. Oczywiście może się zdarzyć (tak jak w przypadku wtyczki clover), że licencja na komercyjne oprogramowanie do użycia niekomercyjnego poprzez Maven wygaśnie i nie będzie możliwe jego wykorzystanie, aż do uaktualnienia licencji w repozytorium. W takiej sytuacji nie pozostaje nic innego jak skorzystanie z innej, alternatywnej wtyczki.

Pomyślne uruchomienie wtyczki checkstyle:checkstyle zapisze wynik swego działania w katalogu target/site. Otwórz checkstyle.html, aby zobaczyć jak niewielkim kosztem można wygenerować ciekawy raport o stanie jakości projektu. Oczywiście przydatnymi wtyczkami mogą być również javadoc (wykonaj cel javadoc:javadoc i otwórz target/javadoc/index.html), czy wręcz site (uruchom site i otwórz target/site/index.html).

Możliwe jest również uruchomienie kilku wtyczek i ich celi w jednym uruchomieniu Maven, np.
mvn checkstyle:checkstyle javadoc:javadoc site
W takiej sytuacji Maven wyznaczy zależności do pobrania i będzie pobierał je raz. Jeśli zależność znajduje się już w repozytorium lokalnym Maven (zazwyczaj) nie podejmuje ponownego pobrania ze zdalnego repozytorium, co znacznie skraca czas wykonania polecenia.

Utworzenie i instalacja biblioteki projektu w lokalnym repozytorium to uruchomienie wtyczki install.
mvn install
Poniżej znajdują się pozostałe, ważne wtyczki Maven dostępne w domyślnej konfiguracji.
  • test (mvn test) spowoduje uruchomienie testów jednostkowych (domyślnie zapisywanych w src/test/java)
  • package (mvn package) - uruchamia proces tworzenia paczki dystrybucyjnej i zapisuje wynik działania w katalogu target
  • clean (mvn clean) - usuwa katalog target
Przejrzyj dokumentację Mavena w poszukiwaniu innych wtyczek. Centralne repozytorium wtyczek i zależności - http://www.ibiblio.org/maven2/ - jest podstawowym źródłem ich dystrybucji.