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 &amp;&amp; zip -9 resources.xrs *.xrc *.png &amp;&amp; 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)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/