Po co mi to?
Niestety, większość programistów PHP nie tylko nigdy nie używała innych poważniejszych języków programowania takich, jak C czy Java, ale również nie pracowała na poważnie z systemem innym, niż Windows. Wynosimy z niego przyzwyczajenie do instalatorów, tymczasem każdy, kto trochę dłużej pracował na Linuksie czy jakimś BSD, zna magiczną sekwencję komend, którą można zainstalować niemal wszystko, mając dostęp do kodu źródłowego:
./configure make make install
Duże aplikacje mogą składać się z setek, a nawet tysięcy plików kodu. Ich ręczna kompilacja to masochizm, dlatego wymyślono tzw. systemy budowania, gdzie programista może zapisać reguły kompilacji źródeł oraz budowania z nich gotowego pliku wykonywalnego. Później wystarczy tylko wpisać make, a odpowiedni program zajmie się resztą. My możemy w tym czasie iść zaparzyć sobie herbatę, a po powrocie mamy na naszym dysku skompilowany i działający program.
W PHP pojęcie kompilacji występuje jedynie na etapie wewnętrznego przetwarzania skryptu; zwykły użytkownik nie musi nawet wiedzieć, że takie coś ma miejsce. W związku z tym system budowania nie jest aż tak bardzo potrzebny. Wiele skryptów wystarczy jedynie skopiować na serwer, wgrać bazę danych i skonfigurować połączenie z nią. Wobec tego po co zawracam Wam tym głowę? Wystarczy zmienić punkt widzenia, aby zrozumieć - systemy budowania w ogólności służą automatyzowaniu wielu upierdliwych czynności. Podam przykład z życia. Od kilku lat zajmuję się rozwojem różnych bibliotek open-source. Oprócz pisania kodu, wymaga to oczywiście wykonywania wielu czynności administracyjnych. Gdy wydawana jest nowa wersja, trzeba poprawić odpowiednie numerki w plikach, zbudować archiwa instalacyjne, wygenerować dokumentację, wgrać to wszystko na serwer, porobić tagi w repozytorium i napisać jakiegoś newsa na stronę. Robienie tego ręcznie zajmowało często po kilkadziesiąt minut, a niekiedy nawet godziny, tymczasem wszystkie te czynności równie dobrze mógłby wykonać za mnie komputer po podaniu mu numeru wersji i niezbędnych haseł.
Inny przykład to prowadzenie strony internetowej. Na dysku mamy jej lokalną kopię, gdzie nanosimy poprawki, a od czasu do czasu musimy wysłać zmiany na serwer. Chce się komuś robić to ręcznie? Nie sądzę. Także i tutaj możemy sprawić, by system budowania automatycznie robił kopię zapasową, kopiował wszystkie pliki na FTP i czyścił cache. Sądzę, że w codziennej rzeczywistości programistycznej każdy znalazłby rzeczy, które zabierają mu dużo czasu, a które można by zlecić komputerowi. Jak? Opanowując system budowania Phing, tym bardziej że nie jest to wcale trudne.
Czym jest Phing?
Wspomniany już kilkakrotnie Phing to system budowania napisany specjalnie z myślą o projektach PHP i wzorowany na javowym systemie ant. Reguły budowania zapisujemy tutaj jako plik XML zapisywany w katalogu głównym projektu. Są one podzielone na tzw. cele (ang. targets). Każdy cel składa się z jednego lub większej liczby zadań, które są fizycznymi akcjami wykonywanymi na naszym kodzie. Ponadto, cel może mieć zależności. Przykładowo, aby wgrać paczki z nowym wydaniem na serwer, musimy je najpierw wygenerować, zatem przed celem opublikuj powinien być wykonany zbuduj. Phing potrafi pilnować zależności tak, by minimalizować ryzyko popełnienia pomyłki.
Zamiast Phinga możemy wykorzystać oczywiście bardziej uniwersalne narzędzia takie, jak make czy cmake, ale po pierwsze raczej możemy zapomnieć o ich dostępności pod Windowsem, a po drugie mają one uboższy warsztat narzędziowy. Siłą Phinga jest duża lista dostępnych typów zadań, których możemy użyć do tworzenia naszych regułek, z czego wiele z nich jest charakterystycznych wyłącznie dla aplikacji PHP. Wśród niektórych z możliwości można wymienić:
- kopiowanie plików, katalogów,
- tworzenie archiwów GZIP, BZIP2, ZIP,
- tworzenie archiwów PHAR,
- tworzenie pakietów PEAR i PEAR2,
- zarządzanie numerami wersji,
- wysyłanie plików na serwer FTP,
- wysyłanie żądań HTTP, autoryzacja HTTP,
- wysyłanie e-maili,
- generowanie raportów pokrycia kodu testami (PHPUnit),
- wykonywanie testów jednostkowych (PHPUnit),
- generowanie dokumentacji API (PHPDocumentor),
- obsługa systemów kontroli wersji: CVS, SVN, Git,
- instalacja baz danych,
- obsługa SCP i SSH,
- wsparcie dla różnych narzędzi takich, jak PHP_CodeSniffer.
Lista ta jest niepełna, a wraz z rozwojem projektu liczba dostępnych typów zadań jest coraz większa.
Instalacja
Phinga najłatwiej jest zainstalować poprzez PEAR:
pear channel-discover pear.phing.info pear install phing/phing
Można także ściągnąć samodzielne paczki ze strony projektu, ale to będzie wymagać doinstalowania ręcznego również kilku pakietów PEAR, jeśli zamierzamy korzystać z jakichś bardziej rozbudowanych akcji.
Phing do pracy wymaga przynajmniej PHP 5.2. Warto zaopatrzyć się również w kilka przydatnych rozszerzeń:
- bz2,
- ssh2 (jeśli zamierzamy łączyć się przez SSH],
- XDebug (jeśli zamierzamy odpalać PHPUnita),
- zip,
- zlib
Po zainstalowaniu należy upewnić się, że Phing działa prawidłowo, próbując wpisać w konsoli:
phing
Jeśli wszystko poszło dobrze, powinniśmy ujrzeć narzekania o braku pliku build.xml w bieżącym katalogu. Gdyby coś nie działało, problemy mogą być następujące:
- nie mamy intepretera PHP w systemowej ścieżce plików wykonywalnych,
- nie mamy skryptu phing w systemowej ścieżce plików wykonywalnych,
- PHP nie ma potrzebnych modułów,
- wersja PHP jest zbyt stara.
Jak używać?
Pora przystąpić do pracy z Phingiem. Widzieliśmy już narzekania, że musimy stworzyć jakiś plik build.xml. To w nim zapiszemy wszystkie reguły. Umieszcza się go zwykle w katalogu głównym projektu tak, aby był do niego łatwy dostęp. W tym wpisie pokażę, jak zbudować regułki dla przykładowej aplikacji, gdzie chcemy utworzyć archiwa z bieżącą wersją kodu.
Zaczynamy od stworzenia schematu pliku:
<?xml version="1.0" encoding="UTF-8"?> <project name="FooBarJoe" default="dist"> <!-- tutaj definiujemy cele --> </project>
Znacznik project definiuje nasz projekt. Musimy podać jego nazwę oraz wskazać domyślny cel, który będzie aktywowany, jeśli przy wywyoływaniu Phinga nie podamy żadnego. Następnym krokiem jest dodanie przynajmniej jednego celu. Musimy zrobić sobie jakiś tymczasowy katalog, w którym ugotujemy nasze archiwa i to właśnie wykona pierwszy cel, prepare:
<target name="prepare"> <echo msg="Making the build directories" /> <mkdir dir="./build" /> <mkdir dir="./output" /> </target> <target name="dist" depends="prepare"> <echo msg="Dummy target" /> </target>
Wewnątrz znacznika target umieszczamy znaczniki reprezentujące kolejne zadania (ang. tasks). Skorzystaliśmy tu na razie z najprostszych z nich. echo wypisuje komunikat na konsoli, zaś mkdir pozwala utworzyć nowy katalog o podanej nazwie. Konfigurując cel, możemy podać także opcjonalny atrybut depends, w którym po przeciwnku wymieniamy wszystkie cele, od których ten konkretny jest zależny, a zatem które muszą wykonać się najpierw. Spróbujmy teraz odpalić Phinga w katalogu z naszym plikiem build.xml:
phing
Tym razem powinny pojawić się nam nieco inne dane:
Buildfile: /sciezka/do/projektu/build.xml
Nazwa projektu >
prepare:
[echo] Making the build directories
[mkdir] Created dir: /sciezka/do/projektu/build
[mkdir] Created dir: /sciezka/do/projektu/output
dist:
[echo] DummyJak widać, Phing odpalił domyślny cel dist, wcześniej wykonując zależność prepare, która utworzyła nam dwa katalogi. Na koniec otrzymaliśmy ładny raport informujący o tym, co zostało zrobione. Aby uruchomić inny cel, po prostu podajemy jego nazwę w linii komend:
phing prepare
Tym razem wyświetlił nam się sam komunikat "echo". Zadanie mkdir wcześniej sprawdza czy katalogi przypadkiem już nie istnieją i nie wykonuje się bez potrzeby.
Dodajmy teraz cel build, który skopiuje do utworzonych katalogów odpowiednie pliki, przygotowując całość do spakowania:
<target name="build" depends="prepare"> <echo msg="Copying files" /> <copy todir="./build/src"> <fileset dir="./src"> <include name="**/*.php" /> </fileset> </copy> <copy todir="./build/web"> <fileset dir="./public_html"> <include name="**/*.php" /> </fileset> </copy> <copy file="LICENSE" tofile="./build/LICENSE" overwrite="true"/> <copy file="README" tofile="./build/README" overwrite="true"/> </target>
Zadanie copy zajmuje się szeroko pojętym kopiowaniem. Możemy kopiować pojedyncze pliki, jak i całe katalogi, a co więcej - mamy możliwość wykorzystania całkiem precyzyjnego narzędzia filtrów. W pierwszych dwóch przypadkach zaznaczamy, że do katalogu ./build/src oraz ./build/web/ mają trafić pliki o rozszerzeniu PHP z ./src oraz ./public_html, z uwzględnieniem również podkatalogów (podwójne gwiazdki w dyrektywie include).
Jeśli dany plik istnieje już w katalogu budowania, Phing zastępuje go wyłącznie wtedy, gdy jest starszy od kopiowanej wersji pliku. Możemy jednak wymusić jego nadpisywanie przy pomocy atrybutu overwrite. Innym przydatnym atrybutem może być includeemptydirs. Jeśli ustawimy go na true, Phing skopiuje także puste katalogi, które domyślnie są pomijane.
Pora zająć się akcją dist, której celem jest utworzenie archiwów w kilku najpopularniejszych formatach. Użyjemy do tego zadań tar, zip oraz pharpackage:
<target name="dist" depends="build"> <echo msg="Creating archives..." /> <tar destfile="./output/projekt.tar.gz" compression="gzip"> <fileset dir="./build"> <include name="**" /> </fileset> </tar> <tar destfile="./output/projekt.tar.bz2" compression="bzip2"> <fileset dir="./build"> <include name="**" /> </fileset> </tar> <zip destfile="./output/projekt.zip"> <fileset dir="./build"> <include name="**" /> </fileset> </zip> <pharpackage destfile="./output/projekt.phar" basedir="./build/"> <fileset dir="./build/"> <include name="**" /> </fileset> <metadata> <element name="name" value="Mój projekt" /> <element name="copyright" value="Adam Kowalski" /> <element name="license" value="Nazwa licencji" /> </metadata> </pharpackage> </target>
Zadanie tar generuje archiwum TAR i opcjonalnie kompresuje je odpowiednim algorytmem. Głównym argumentem jest lista plików do kompresji - w naszym przypadku jest to po prostu zawartość katalogu ./build/. Do pracy potrzebujemy zainstalowanych rozszerzeń zlib oraz bz2, a także biblioteki Archive_Tar z PEAR. Bardzo podobnie działa zadanie zip, z tym że z oczywistych względów nie musimy tu wybierać algorytmu kompresji.
Nieco inaczej sprawa ma się z generowaniem archiwów PHAR. Przypomnę, że jest to specjalny format dystrybucji bibliotek i aplikacji PHP pozwalający zapakować całość do postaci jednego, wielkiego pliku archiwum. Opcjonalnie możemy także go skompresować oraz wykonać jego sygnaturę. Musimy przede wszystkim wybrać nazwę tworzonego archiwum oraz listę plików, które chcemy do niego dołączyć. Musimy też określić atrybut basedir - Phing potrzebuje tego, żeby poprawić jakieś ścieżki do plików w archiwum tak, by nie było tam śmieci z naszego środowiska. W sekcji metadata możemy umieścić meta-informacje o naszym archium dla potomności. Gdy teraz wywołamy Phinga, utworzy on nam katalogi budowania, skopiuje do nich pliki projektu i przygotuje cztery archiwa. Cel osiągnięty.
Dodajemy właściwości
Patrząc na nasze dotychczasowe osiągnięcia nietrudno zauważyć, że nasz plik build.xml jest mało elastyczny. W końcu z bliżej nieokreślonych powodów możemy chcieć zmienić katalogi robocze (to się zdarza), ale najważniejszym brakiem jest niemożność wprowadzenia dynamicznie konfigurowanego numeru wersji. Jednak liznęliśmy dopiero czubek góry lodowej. Phing posiada obsługę tzw. właściwości, które można wczytać z dodatkowego pliku i wykorzystać we wszystkich niemal miejscach. Rzeczony dodatkowy plik nosi nazwę build.properties i umieszczony jest dokładnie w tym samym katalogu. Ma bardzo prostą budowę podobną do plików INI:
# Configure the project definitions here project.name=Moj projekt project.filename=projekt project.license=New BSD # Configure your build directories here project.directory.build=./build project.directory.output=./output
Możemy utworzyć sobie tutaj tak wiele właściwości, ile tylko potrzebujemy, jednak skoro już się za to bierzemy, zwracam uwagę, że warto je sobie od początku jakoś logicznie uporządkować, aby się w tym nie pogubić. Ich późniejsze wykorzystanie w kodzie jest niezwykle proste. Osadzamy je, wpisując zamiast stałej wartości ciąg ${nazwawłaściwości}. Oto, jak możemy przerobić cel dist do korzystania z atrybutów:
<target name="dist" depends="build"> <echo msg="Creating archives..." /> <tar destfile="${project.directory.output}/${project.filename}-${project.version}.tar.gz" compression="gzip"> <fileset dir="${project.directory.build}"> <include name="**" /> </fileset> </tar> <tar destfile="${project.directory.output}/${project.filename}-${project.version}.tar.bz2" compression="bzip2"> <fileset dir="${project.directory.build}"> <include name="**" /> </fileset> </tar> <zip destfile="${project.directory.output}/${project.filename}-${project.version}.zip"> <fileset dir="${project.directory.build}"> <include name="**" /> </fileset> </zip> <pharpackage destfile="${project.directory.output}/${project.filename}.phar" basedir="${project.directory.build}"> <fileset dir="${project.directory.build}"> <include name="**" /> </fileset> <metadata> <element name="name" value="${project.name}" /> <element name="copyright" value="${project.copyright}" /> <element name="license" value="${project.license}" /> </metadata> </pharpackage> </target>
Teraz całość można bardzo prosto konfigurować na nasze potrzeby. Warto zainteresować się też zadaniem version. Upraszcza ono zarządzanie numerami wersji, które trzymane są w pliku version.txt. Zadanie zwiększa numer wersji w pliku, a następnie rejestruje go jako właściwość. Dla wielu osób może to być bardzo przydatne rozwiązanie.
Subiektywnym okiem
Liznęliśmy zaledwie czubek możliwości Phinga, ale mam nadzieję, że udało się mi zainteresować Was tym projektem i rzeczami, które można za jego pomocą osiągnąć. Sam z braku czasu zacząłem poznawać go dopiero niedawno, mimo iż szykowałem się do tego już od wielu miesięcy. Program jest bardzo prosty w obsłudze; wystarczy przejrzeć sobie listę akcji i w zasadzie w jeden dzień można stworzyć naprawdę fajny warsztat narzędziowy. Ostatnio zacząłem przygotowywać taki dla projektów grupy Invenzzia w związku z jej reaktywacją. Będzie on potrafił dodatkowo automatycznie umieścić wszystkie pliki na serwerze, a docelowo wykonać dokumentację i przeprowadzić wszystkie testy. Ponadto, tworzę dla niego zestaw akcji do zarządzania repozytorium Git, lecz tu niestety trochę mnie przyhamowało z powodu braku obsługi opcji --no-ff w zadaniu GitMerge. Ale co się odwlecze, to nie uciecze. Wstępną wersję wrzuciłem do repozytorium invenzzia-tools, a w miarę czasu będą się w niej pojawiać coraz to nowe opcje. Myślę, że może się to przydać wielu czytelnikom Dzienników zyxowych i nie tylko. Phinga warto poznać niezależnie od tego czy robimy proste stronki na zamówienie, czy projekty open-source z regularnymi wydaniami. Myślę, że każdy znajdzie dla niego niejedno ciekawe zastosowanie.






Napisał Marek w niedzielę, 23 stycznia 2011 o 20:40
Świetny tekst.
Dotychczas musiałem sobie radzić tworząc jakieś proste dodatkowe skrypty i używając hooków gita. Nawet nie wiedziałem, że istnieje takie narzędzie.
Dzięki.