07 kwietnia 2006

Podstawowe pojęcia Maven2: sekwencje, etapy, wtyczki i zadania

4 komentarzy
Tworzenie oprogramowania zawsze wiąże się z tymi samymi etapami (phases). Nie ma to związku z zastosowanymi technologiami i sprowadza się do wykonywania ustalonych sekwencji (lifecycle), aż do osiągnięcia zamierzonego celu - finalnego uruchomienia aplikacji w środowisku produkcyjnym u klienta. Zanim jednak to nastąpi, niezliczone ilości razy wykonujemy te same etapy przy wsparciu dedykowanych narzędzi (plugins), które składają się z funkcjonalności wspomagających wykonanie zadań (goals) w określonych etapach tworzenia oprogramowania. Jedną z sekwencji mogłaby być: generowanie kodu pomocniczego (nie mylić z samym programowaniem, który jest zadaniem niezastępowalnym przez narzędzia i musi być wykonane przez człowieka), kompilacja, testowanie jednostkowe, przygotowanie wersji dystrybucyjnej, testowanie integracyjne, instalacja i ostatecznie uruchomienie (nie wyróżniam samego etapu programowania, który według mnie jest nie do obsłużenia przez żadne narzędzia, tzn. któryś z etapów wymaga nasz interwencji, czy to jest modelowanie, czy samo programowanie, czy inna czynność inicjująca proces tworzenia oprogramowania). Oczywiście wyróżnić można mniej złożone sekwencje wykonywane przez pojedyńczego członka zespołu, jak np. generowanie kodu pomocniczego, kompilacja i testowanie jednostkowe, wykonywane częściej niż inne bardziej złożone. Pozostałymi etapami zainteresowani mogliby być inni członkowie zespołu, np. testerzy czy wdrożeniowcy. W ten sposób określamy pewien z góry ustalony schemat wytwarzania oprogramowania bez względu na złożoność technologiczną projektu informatycznego i wszystkie inne projekty są jedynie jego kopią (czasami Różnica może polegać na długości czasu w jakim wymienione etapy są wykonywane. Mimo, że najczęściej spotykam się z komentarzami, że to czas jest główną przyczyną porażki wielu projektów informatycznych, to uważam, że główny ciężar odpowiedzialności spada na zastosowane narzędzia, którymi posługują się członkowie zespołu. Obecnie jestem przekonany, że brak wykorzystania pewnych narzędzi jest wyraźnym sygnałem o marnotrastwie czasu. Jednym z takich narzędzi jest z pewnością Maven2. Przyjrzyjmy się dlaczego.

Czytając początkowe zdania artykułu, Twoją uwagę mogły przykuć angielskie terminy w nawiasach - lifecycle, phase, plugin oraz goal. Wplotłem je świadomie, ponieważ reprezentują podstawowe pojęcia Maven2, które odpowiadają pojęciom projektowym - sekwencja (również cykl), etap, wtyczka (również narzędzie) oraz zadanie, odpowiednio. Zrozumienie działania Maven2 to zrozumienie istoty tych pojęć. Usystematyzowanie działań podczas tworzenia oprogramowania, wprowadzone wdrożeniem Maven2, ma zauważalnie pozytywny efekt na kondycję i postępy projeku. Pozwala skorzystać z dobrych praktyk projektowych i programistycznych bez dodatkowych nakładów czasowych oraz finansowych na ich dogłębne poznanie. Prostota korzystania z Maven2 ma niebagatelne znaczenie do osiągnięcia zamierzonego celu - ukończenia projektu na czas z jakością tworzenia "z górnej półki". I wszystko za darmo (pomijając aspekt czasu, który wierzę, że dzięki temu artykułowi spadnie do niezbędnego minimum).

Jako narzędzie pretendujące do miana narzędzia wspierającego zarządzanie projektem informatycznym, Maven2 dostarcza 3 predefiniowanie sekwencje projektowe: default, clean oraz site. Każda sekwencja składa się z etapów. Ilość etapów jest specyficzna dla sekwencji, ale istnieje możliwość ich zmiany. Poniższa tabela przedstawia nazwy etapów dla predefiniowanych sekwencji projektowych w Maven2.

Sekwencja
Etap
default
validate
generate-sources
process-sources
generate-resources
process-resources
compile
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
test
package
pre-integration-test
integration-test
post-integration-test
verify
install
deploy
clean
pre-clean
clean
post-clean
site
pre-site
site
post-site
site-deploy

Z sekwencją i poszczególnym jej etapem związane są pewne wtyczki i ich zadania (aka czynności). Jednym z bardzo trafnych stwierdzeń z jakim się spotkałem to określenie Maven jako środowiska do uruchamiania wtyczek, które z kolei dostarczają zadań (porównać to można do wirtualnej maszyny Javy, która jest środowiskiem uruchomieniowym klas, bądź też do kontenera serwletów, które jest środowiskiem dla serwletów). Etap może nie posiadać przypisanego zadania wtyczki, albo może istnieć ich więcej. Istnieje dodatkowo możliwość przypisania zadania wtyczki do etapu sekwencji w konfiguracji projektu (pom.xml) za pomocą znacznika execution (który będzie tematem przewodnim kolejnego artykułu). Sam typ projektu (wartość znacznika packaging, tj. pom, jar, maven-plugin, ejb, war, ear, rar, par, ejb3) również wyznacza określone wtyczki i ich zadania do poszczególnych etapów. Poniższa tabela przedstawia nazwy etapów dla wspomnianych typów packaging.

packagingsekwencjaetapwtyczka:zadanie
pomdefaultpackageorg.apache.maven.plugins:maven-site-plugin:attach-descriptor


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
jardefaultprocess-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


packageorg.apache.maven.plugins:maven-jar-plugin:jar


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
maven-plugindefaultgenerate-resourcesorg.apache.maven.plugins:maven-plugin-plugin:descriptor


process-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


package
org.apache.maven.plugins:maven-jar-plugin:jar
org.apache.maven.plugins:maven-plugin-plugin:addPluginArtifactMetadata


install
org.apache.maven.plugins:maven-install-plugin:install
org.apache.maven.plugins:maven-plugin-plugin:updateRegistry


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
ejbdefaultprocess-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


packageorg.apache.maven.plugins:maven-ejb-plugin:ejb


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
wardefaultprocess-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


packageorg.apache.maven.plugins:maven-war-plugin:war


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
eardefaultgenerateresourcesorg.apache.maven.plugins:maven-ear-plugin:generate-application-xml


process-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


packageorg.apache.maven.plugins:maven-ear-plugin:ear


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
rardefaultprocess-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


packageorg.apache.maven.plugins:maven-rar-plugin:rar


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
pardefaultprocess-resourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-test-resourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


packageorg.apache.maven.plugins:maven-par-plugin:par


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy
ejb3defaultprocessresourcesorg.apache.maven.plugins:maven-resources-plugin:resources


compileorg.apache.maven.plugins:maven-compiler-plugin:compile


process-testresourcesorg.apache.maven.plugins:maven-resources-plugin:testResources


test-compileorg.apache.maven.plugins:maven-compiler-plugin:testCompile


testorg.apache.maven.plugins:maven-surefire-plugin:test


packageorg.apache.maven.plugins:maven-ejb3-plugin:ejb3


installorg.apache.maven.plugins:maven-install-plugin:install


deployorg.apache.maven.plugins:maven-deploy-plugin:deploy

Uruchomienie Maven2 odbywa się na 2 podstawowe sposoby, badź ich kombinacje. Pierwszy z nich polega na uruchomieniu polecenia mvn z wyspecyfikowanym etapem. Maven2 wymaga, aby nazwy etapów były unikatowe, co jednoznacznie określa sekwencję i poszczególne etapy poprzedzające ten zadany. Znajomość etapów określa wtyczki i ich zadania, które zostaną wykonane zgodnie z kolejnością etapów w sekwencji. Pamiętajmy, że istotne znacznie ma konfiguracja projektu (pom.xml) w sekcji packaging i executions (temat kolejnego artykułu). Obie sekcje wyznaczają ostateczną listę wtyczek i zadań do wykonania, a ich brak w pom.xml wskazuje na predefiniowaną konfigurację Maven2 przedstawioną w tabelach powyżej.

Rozpoczynając pracę, Maven2 rozpoczyna od wyznaczenia sekwencji i jej etapów. Następnie mapuje je na odpowiednie zadania wtyczek (w tym celu odczytuje predefiniowaną konfigurację sekwencji i nakłada na to definicję etapów związaną z packaging i executions) i ostatecznie wykonuje zadania zgodnie z ich kolejnością wyznaczoną przez etapy w sekwencji. Kolejność wykonywania zadań można prześledzić podczas uruchomienia mvn, gdzie każda wtyczka i jej zadanie wypisywane są w schemacie [INFO] [wtyczka:zadanie], np. [INFO] [jar:jar]. Poza wyspecyfikowaniem etapu istnieje również możliwość uruchomienia Maven2 z konkretnym zadaniem wtyczki. Definicja domyślnie wykonywanego zadania bądź etapu w projekcie może również zostać wyspecyfikowana w sekcji <build> jako <defaultGoal> w pom.xml. Jeśli nie podano etapu bądź zadania na linii poleceń, Maven2 oczekuje wspomnianej konfiguracji projektu w sekcji <defaultGoal>.

Przyjrzyjmy się kilku przykładom dla zobrazowania przedstawionego materiału. Zakłada się, że wykonanie poniższych poleceń odbywa się w projekcie obsługiwanym przez Maven2 (innymi słowy istnieje plik pom.xml).
  1. Uruchamiamy polecenie mvn bez wyspecyfikowanego etapu bądź zadania wtyczki.
    $ mvn
    [INFO] Scanning for projects...
    [INFO] ----------------------------------------------------------------------------
    [ERROR] BUILD FAILURE
    [INFO] ----------------------------------------------------------------------------
    [INFO] You must specify at least one goal. Try 'install'
    [INFO] ----------------------------------------------------------------------------
    ...
    Osobiście uważam, że komunikat jest mylący, gdyż install nie jest zadaniem (goal), ale etapem (phase) w sekwencji (lifecycle) default. Należy zatem rozumieć ten komunikat jako wskazanie do wykonania wszystkich zadań przypisanych do etapów w sekwencji default, począwszy od pierwszego a skończywszy na install.

    Sprawdźmy, czy istnieje Polskie tłumaczenie, które może być bardziej adekwatne.
    $ mvn -Duser.language=pl_PL
    [INFO] Scanning for projects...
    [INFO] ----------------------------------------------------------------------------
    [ERROR] BUILD FAILURE
    [INFO] ----------------------------------------------------------------------------
    [INFO] You must specify at least one goal. Try 'install'
    [INFO] ----------------------------------------------------------------------------
    ...
    Niestety w tym przypadku takie tłumaczenie nie istnieje i uruchomienie z opcją -e również nie dostarcza więcej informacji. Niektóre jednak komentarze Maven2 zostały spolonizowane.

  2. Uruchommy polecenie mvn z nazwą nieistniejącego etapu, np. etap.
    $ mvn etap
    [INFO] Scanning for projects...
    [INFO] ----------------------------------------------------------------------------
    [ERROR] BUILD FAILURE
    [INFO] ----------------------------------------------------------------------------
    [INFO] Invalid task 'etap': you must specify a valid lifecycle phase, or a goal in the format plugin:goal or pluginGroupId:pluginArtifactId:pluginVersion:goal
    [INFO] ----------------------------------------------------------------------------
    ...
    Komunikat jest tym razem bardziej dokładny i dowiadujemy się, że konieczne jest podanie istniejącego etapu bądź zadania w odpowiednim formacie (temat wtyczek będzie tematem jednego z kolejnych artykułów).

  3. Uruchommy polecenie mvn z poprawnym etapem w sekwencji default, np. test.
    $ mvn test
    [INFO] Scanning for projects...
    [INFO] ----------------------------------------------------------------------------
    [INFO] Building Projekt Demonstracyjny Maven2
    [INFO] task-segment: [test]
    [INFO] ----------------------------------------------------------------------------
    [INFO] [resources:resources]
    ...
    [INFO] [compiler:compile]
    ...
    [INFO] [resources:testResources]
    ...
    [INFO] [compiler:testCompile]
    ...
    [INFO] [surefire:test]
    ...
    [INFO] ----------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ----------------------------------------------------------------------------
    ...
    Jak można zauważyć wykonanie etapu test zakończy się pomyślnie, a w trakcie wykonywania kolejnych etapów zostaną wykonane zadania wtyczek przypisanych do nich.

  4. Uruchommy powyższe polecenie ponownie zmodyfikowawszy wartość packaging w pom.xml, np. maven-plugin.
    $ mvn test
    [INFO] Scanning for projects...
    [INFO] ----------------------------------------------------------------------------
    [INFO] Building Projekt Demonstracyjny Maven2
    [INFO] task-segment: [test]
    [INFO] ----------------------------------------------------------------------------
    ...
    [INFO] [plugin:descriptor]
    ...
    [INFO] [resources:resources]
    ...
    [INFO] [compiler:compile]
    ...
    [INFO] [resources:testResources]
    ...
    [INFO] [compiler:testCompile]
    ...
    [INFO] [surefire:test]
    ...
    [INFO] ----------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ----------------------------------------------------------------------------
    Tym razem wykonane zadania różnią się o początkową wtyczkę plugin. Jest to potwierdzenie wpływu konfiguracji sekcji packaging na listę zadań do wykonania.

  5. Uruchommy polecenie mvn z podaniem wtyczki surefire bez wskazania jej zadania.
    $ mvn surefire
    [INFO] Scanning for projects...
    [INFO] ----------------------------------------------------------------------------
    [ERROR] BUILD FAILURE
    [INFO] ----------------------------------------------------------------------------
    [INFO] Invalid task 'surefire': you must specify a valid lifecycle phase, or a goal in the format plugin:goal or pluginGroupId:pluginArtifactId:pluginVersion:goal
    [INFO] ----------------------------------------------------------------------------
    [INFO] For more information, run Maven with the -e switch
    [INFO] ----------------------------------------------------------------------------
    Zgodnie z komunikatem parametr bez dwukropka traktowany jest jako nazwa etapu, a skoro taki nie istnieje Maven2 kończy działanie z komunikatem błędu.

  6. Uruchommy polecenie mvn z wyspecyfikowaną wtyczką surefire z zadanym zadaniem, np. test, który mogliśmy zauważyć uruchamiany we wcześniejszych przykładach (wraz z innymi poprzedzającymi zadaniami).
    $ mvn surefire:test
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'surefire'.
    [INFO] ----------------------------------------------------------------------------
    [INFO] Building Projekt Demonstracyjny Maven2
    [INFO] task-segment: [surefire:test]
    [INFO] ----------------------------------------------------------------------------
    [INFO] [surefire:test]
    ...
    [INFO] ----------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ----------------------------------------------------------------------------
    Na uwagę zasługuje fakt, że wykonanie wtyczki nie zostało poprzedzone wykonaniem żadnego innego zadania, co pozwala na uruchomienie wtyczek pojedyńczo. Jest to szczególnie przydatne z wtyczkami, które nie są przypisane do żadnej sekwencji.

  7. Na koniec uruchommy polecenie mvn z 2 etapami należącymi do różnych sekwencji oraz zadanie wtyczki, której wykonanie jest częścią uruchamiania etapów.
    $ mvn clean package surefire:test
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'surefire'.
    [INFO] ----------------------------------------------------------------------------
    [INFO] Building Projekt Demonstracyjny Maven2
    [INFO] task-segment: [clean, package, surefire:test]
    [INFO] ----------------------------------------------------------------------------
    [INFO] [clean:clean]
    ...
    [INFO] [plugin:descriptor]
    ...
    [INFO] [resources:resources]
    ...
    [INFO] [compiler:compile]
    ...
    [INFO] [resources:testResources]
    ...
    [INFO] [compiler:testCompile]
    ...
    [INFO] [surefire:test]
    ...
    [INFO] [jar:jar]
    ...
    [INFO] [plugin:addPluginArtifactMetadata]
    [INFO] [surefire:test]
    ...
    [INFO] ----------------------------------------------------------------------------
    [INFO] BUILD SUCCESSFUL
    [INFO] ----------------------------------------------------------------------------
    Wszystkie etapy oraz zadanie zostały poprawnie rozpoznane przez Maven2. Wykonanie etapów wiąże się z wyznaczeniem wtyczek i ich zadań, a następnie uruchomieniu zgodnie z kolejnością związanych z nimi etapów. Wyjątek stanowi wykonanie zadania surefire:test, który jest wykonany zgodnie z jego pozycją na linii poleceń, czyli po pomyślnym uruchomieniu zadań związanych z etapami.

Znajomość wtyczek i dostarczanych przez nie zadań jest istotnym elementem zrozumienia działania Apache Maven. Wersja druga projektu - Maven2 - wprowadza kolejne pojęcia - sekwencja (lifecycle) oraz etap (phase). Wyróżniamy 3 predefiniowane sekwencje z zadanymi etapami, do których przypisane są zadania wtyczek. Dzięki takiemu podejściu projekty mogą skorzystać z dobrych praktyk inżynierskich podnoszących jakość projektu niewielkim kosztem. Znajomość Maven2 i jego konfiguracji oraz umiejętność zrozumienia zawartości pom.xml pozwala na łatwe wdrożenie nowych osób do projektu z punktu widzenia jego zarządzania. Poza samym tworzeniem elementów składowych projektu - klasy, konfiguracje, modele, etc. - jest to kluczowy składnik wpływający na czasowe zakończenie projektu. Znosi to również konieczność wprowadzania tych samych ustawień z projektu na projekt (nawet mimo możliwości ich kopiowania, które zazwyczaj kończy się błędami trudnymi do zdiagnozowania). Temat jest stosunkowo zawiły, ale sądzę, że lektura artykułu pozwoli na łatwiejsze zrozumienie działania Maven2.

ADNOTACJA: Nie spotkałem się jeszcze z polskimi odpowiednikami pojęć - lifecycle, phase oraz goal w kontekście Maven, więc pokusiłem się na ich własne spolszczenie. Jeśli znasz lepsze dla nich polskie tłumaczenia, koniecznie się ze mną skontaktuj. Jako rekompensatę mogę jedynie obiecać opublikowanie imienia i nazwiska wraz z ciekawymi propozycjami w kolejnych artykułach.

Komentarze, spostrzeżenia mile widziane. Skorzystaj z możliwości komentowania artykułu, poniżej, albo wyślij komentarz na moją skrzynkę.