Bakefile: shell rule

O systemie budowania aplikacji bakefile, generującym odpowiednie pliki dla make i projekty dla różnych środowisk IDE, pisałem już w przeszłości. Jest to wspaniale narzędzie, szczególnie przy tworzeniu multi platformowego 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 zamierzonego. W Windowsie problem nie istnieje bo wszystko „leci” w jednej konsoli, w której odpalono nmake.

Aby program make wykonał polecenia w tej samej kopii 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 kilu 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 nieczytelne.

W takim wypadku najlepiej byłoby podawać kolejne polecenia i komendy w osobnych liniach, a sam bakefile zająłby się odpowiednią transformacją 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.

<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 prostsze rozszerzenie standardowej reguły action, gdzie zawartość tagu command jest podawana odpowiednim transformacjom, 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, możemy w plikach bakefile wykorzystać proste wyrażenia i funkcje tego języka, dzięki czemu system jest bardzo elastyczny i można wiele przydatnych funkcji i możliwości stworzyć.

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 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 w we wszystkich formatach wyjściowych bakefile, co dogłębnie można zobaczyć na plikach 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 rodzaju wyjściowych 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” ostrzeżenia o zaistniałej sytuacji, 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 ;)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *