Bakefile: shell rule
• tech • 754 słowa • 4 minuty czytania
O systemie budowania aplikacji bakefile, generującym odpowiednie pliki dla narzędzi typu make i projekty dla różnych środowisk IDE, pisałem już w przeszłości. Jest to wspaniale narzędzie, szczególnie przydatne przy tworzeniu multiplatformowego oprogramowania.
Dosyć często zdarza się, że chcemy dorzucić do makefile jakieś niestandardowe reguły oparte na wykonaniu kilku poleceń w powłoce systemowej. Bakefile zawiera odpowiednie mechanizmy do takich celów - reguła action, która pozwala wykonać dowolną komendę lub polecenie, moduły do kopiowania plików, katalogów, drzew katalogów - moduł datafiles (dostarczany w standardzie z bakefile), tworzenie plików wynikowych lokalizacji (mo) opartych na gettext, i wiele innych.
Niestety nie ma nic przyjaznego i wygodnego do wykonania kilku poleceń shellowych. Można wykorzystać regułę action, ale ta niestety posiada pewne ograniczenia. Pokrótce, chcemy zbudować plik zasobów (wx’owych) - resources.xrs
, który de facto jest zwykłym archiwum zawierającym m.in. pliki xrc. Najprościej zdefiniować następującą regułę:
<action id="xrs">
<dependency-of>all</dependency-of>
<command>
cd ../resources
zip -9 resources.xrs *.xrc *.png
cd ../build
</command>
</action>
Co w wynikowym pliku makefile zostanie przedstawione w taki sposób:
xrs:
cd ../resources
zip -9 resources.xrs *.xrc *.png
cd ../build
O ile wersja makefile dla windowsowego VS będzie działać bez problemowo, to wersja pod uniksowe narzędzia rodziny GNU już niezupełnie. Jest to związane z budową programu make
.
W wersji gnu, każde polecenie umieszczone w definicji wykonywane jest w oddzielnej kopii shella, co skutkuje nieprzewidywanym działaniem, różnym od tego zamierzonego. W Windowsie problem nie istnieje bo wszystko “leci” w jednej konsoli, w tej w której odpalono nmake
.
Aby program make
wykonał polecenia w tej samej instancji shella, powinny one być zawarte w jednej linii, oddzielone od siebie średnikiem. Możliwe jest rozdzielnie wiersza na kilka linii wykorzystując znak kontynuacji \
. Niestety dodanie tak spreparowanego wiersza do pliku bakefile nie rozwiąże zbytnio problemu, bo wtedy dla odmiany pod windoswem wystąpią problemy. Umieszczanie dwóch wersji z warunkiem if
również jest chybionym pomysłem.
Jest jedno proste i skuteczne rozwiązanie - wykorzystanie operatora &&
, który we wszystkich shellach działa identycznie. Można, więc bezproblemowo wykorzystać ten operator do połączenia kolejnych poleceń:
<action id="xrs">
<dependency-of>all</dependency-of>
<command>
cd ../resources && zip -9 resources.xrs *.xrc *.png && cd ../build
</command>
</action>
Rozwiązanie to wykorzystywałem do tej pory w kilku projektach. Jest ono idealne dla kilku prostych poleceń. Niestety, gdy niezbędne jest wywołanie dużo większej ich liczby lub przekazanie skomplikowanych parametrów, staje się ono niezbyt wygodne i do tego nieczytelne.
W takim wypadku najlepiej byłoby podawać kolejne polecenia i komendy w osobnych liniach, a sam bakefile zająłby się transformacją kodu do odpowiedniego formatu akceptowanego przez daną powłokę.
Taki był mój cel, dlatego pokusiłem się o stworzenie prostej reguły shell, w której polecenia możemy podać w kilku wierszach. Wszystkie i tak zostaną wykonane w jednej i tej samej instancji shella. Definicja tej reguły wygląda tak:
<define-rule name="shell" extends="action">
<define-tag name="command">
<set var="__command">
<if cond="TOOLSET=='unix'">
$(value.replace('\n', '; \\\n'))
</if>
<if cond="TOOLSET=='win32'">
$(value)
</if>
</set>
</define-tag>
</define-rule>
W istocie jest to proste rozszerzenie standardowej reguły action, gdzie zawartość tagu command
jest poddawana odpowiednim przekształceniom w zależności od platformy przeznaczenia generowanego formatu wyjściowego. Dla uniksowych toolsów dodajemy na końcu każdej linii średnik i znak kontynuacji, a dla windowsowych przepisujemy bez zmian.
Bakefile napisane jest w Pythonie, zatem możemy w plikach bakefile wykorzystać proste wyrażenia i funkcje tego języka, dzięki czemu system jest bardzo elastyczny i pozwala na stworzenie wielu przydatnych funkcji.
Droga do prostego wyrażenia
value.replace('\n', '; \\\n')
była długa, w skrajnym wypadku było nawet
'; \\\n'.join([x.strip() for x in value.splitlines()])
ale dobrze się skończyło ;)
Teraz możemy łatwo i czytelnie stworzyć definicje dla narzędzi make, oparte na wykonaniu kilku poleceń powłoki:
<shell id="xrs">
<dependency-of>all</dependency-of>
<command>
cd ../resources
zip -9 $(OUTPUTDIR)/../resources.xrs *.xrc
zip -9 $(OUTPUTDIR)/../resources.xrs images/*.png
cd ../build
</command>
<clean-files>$(OUTPUTDIR)/resources.xrs</clean-files>
</shell>
Przy okazji, nie zapominajmy po sobie sprzątać! Co w naszym przypadku czyni tag clean-files
, który dodaje swoją zawartość do listy plików do usunięcia w czasie wykonania make clean
.
I tutaj mała uwaga. Nie wszystkie dostępne tagi opisane w rozdziale 4 dokumentacji bakefile są możliwe do wykorzystania we wszystkich formatach wyjściowych bakefile. Dogłębnie można się o tym przekonać patrząc w pliki projektów, dla których większość nich jest niedostępna. Użycie takiego tagu kończy się wyrzuceniem w czasie przetwarzania bakefile błędu typu “unknown target tag…”. Może to być uciążliwe, gdy korzystamy z hurtowego, wsadowego generowania przez bakefile_gen
kilku rodzajów formatów plików wyjściowych.
Dziwne jest to, że w definicjach formatów dla nieobsługiwanych tagów twórcy nie zdefiniowali chociażby “wyplucia” jakiegoś ostrzeżenia o zaistniałej sytuacji, podobnie jak to ma miejsce na przykład w przypadku reguły action lub tagu depends-on-file
dla plików projektu. Na pewno byłoby to dużo lepszym rozwiązaniem niż przerwanie przetwarzania. Może należy im przypomnieć o tym problemie ;)
Komentarze (0)