CI/CD projektów PHP na Jenkinsie – Automatyzacja

Mimo, że PHP jest językiem interpretowanym a kod PHP nie wymaga kompilacji, to developerzy przeprowadzają np. generację lub transformację kodu autoloadera. Doskonałym narzędziem służącym do zautomatyzowania procesu budowy oprogramowania jest Apache Ant. To co różni Ant i np.  znany z Linuksa Make jest to, że Ant używa plików w formacie XML do opisu procesu budowy i jego zależności, podczas gdy Make ma własny format Makefile. Projekt Ant jest w związku z tym przenośny, Make nie. Domyślnie plik XML  w Ant nazywa się build.xml.

Rozważmy następujący projekt umieszczony w katalogu /root:

Katalog build zawiera pliki konfiguracyjne XML dla PHP_CodeSniffer (phpcs.xml), PHPMD (phpmd.xml). PHP_CodeSniffer i PHPMD to narzędzia do statycznej analizy kodu PHP, którym przyjrzymy się bliżej w kolejnym artykule o Jenkinsie i PHP, dotyczącym tzw. Continous Inspection (ciągłej inspekcji). Plik phpunit.xml (lub phpunit.xml.dist) to plik konfiguracyjny projektu PHPUnit. Pliki src/autoload.php, tests/autoload.php zawierają kod autoloadera dla aplikacji i zestaw testów.

Plik Ant bulid.xml definiuje tzw. targety czyli cele (zestawy zadań), które mają zostać wykonane. Podczas uruchamiania Ant istnieje możliwość wybrania, które targety mają zostać uruchomione. Jeżeli nie jest podany żaden target to wykonywany jest domyślny projekt. Każdy target może zależeć od innego targetu, Ant rozpoznaje zależności. Zadanie to kod (np. skrypt), który ma być wykonany.

 

Poniższy tekst pochodzi z tej strony i jest nieco przeze mnie zmodyfikowany. Umieszczam tutaj ten tekst gdyż stanowi on bardzo dobre wprowadzenie do Apache Ant.

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:

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:

Gdzie:

name – nazwa projektu, która nie jest obowiązkowa.
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.
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 (target), które zawierają zadania (task). 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:

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:

 

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 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 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:

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:

Jak widać nie ma 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.

 

Przykłady

Omówię teraz przykładowy plik build.xml, który zawiera kompletny zestaw najpopularniejszych zadań. 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.

 

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 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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *