Borland C++ Compiler/MAKE
Wprowadzenie
edytujMAKE pozwala zautomatyzować proces budowania projektu programistycznego poprzez stawianie odpowiednich zasad dla plików źródłowych i wywoływaniu komend systemowych. Poza tym program ten znacznie przyspiesza proces budowy programu, ponieważ wykonuje operacje tylko na plikach, które zostały zmodyfikowane od czasu ostatniej kompilacji, ma to szczególnie duże znaczenie w przypadku dużych projektów.
Plik Makefile
edytujNajważniejszy plik dla MAKE. W nim znajdują się wszelkie zasady operacji na źródłach, wywołania programów, zmienne i instrukcje odpowiedzialne za zarządzanie budową projektu. Utworzyć go można w najprostszym edytorze tekstowym (np. w systemowym Notatniku). Należy pamiętać jednak, aby plik zapisać jako makefile.mak lub makefile (bez rozszerzenia). To, którą opcję wybierzesz jest już obojętne.
Wywołanie
edytujWywołanie MAKE jest nieco inne niż pozostałych narzędzi Borlanda. W tym przypadku kluczową rolę odgrywa lokalizacja na dysku, z której wywołujesz program. W lokalizacji tej musi się znajdować plik makefile wraz ze skopiowanym (z katalogu \bin naszych FCLT) make.exe. To ważne! Inaczej programy wywoływane z makefile'a nie będą działały poprawnie. Będziesz wywoływać właśnie tę kopię make'a, więc najpierw trzeba wejść w jej lokalizację:
CD <lokalizacja> MAKE [<opcje>] [<wynik>]
- <opcje>
- MAKE ma mało opcji, a ponadto w większości z nich można zastąpić odpowiednimi komendami w makefile'u; więcej na ten temat w rozdziale: Plik projektu i konfiguracja MAKE
- <wynik>
- czyli plik(i) wynikowy/e Twojego projektu, pamiętaj że nie musisz ich tutaj wymieniać
w praktyce wywołanie MAKE może wyglądać tak:
CD c:\Hello MAKE -a -s
Składnia makefile'a
edytujSkładnia makefile to już swego rodzaju język programowania do budowy projektów programistycznych. Możemy rozróżnić jej kilka podstawowych elementów:
- komentarze
- po prostu komentarze, tak jak w innych językach programowania
- zasady
- zasady wg których MAKE dobiera pliki źródłowe i wynikowe po wypełnieniu których wywołuje...
komendy systemowe "siłą" MAKE jest możliwość wywoływania nie tylko narzędzi Borland-a, ale także komend DOS'owych i innych programów takich jak np. kompilator assemblera.
- zmienne
- tak jak w każdym języku programowania "kontenery" do których możemy przypisać łańcuchy znaków.
- dyrektywy
- chociaż będę je opisywał na końcu nie powinny być lekceważone, to jedne z najważniejszych elementów w każdym języku programowania, tak samo jest i tutaj.
A tak wygląda przykładowy makefile, z którego korzystasz na co dzień prawdopodobnie nawet o tym nie wiedząc, czyli builtins.mak z BIN'a:
# # Inprise C++Builder - (C) Copyright 1999 by Borland International # CC = bcc32 RC = brcc32 AS = tasm32 !if $d(__NMAKE__) CXX = bcc32 -P CPP = bcc32 -P !endif .asm.obj: $(AS) $(AFLAGS) $&.asm .c.exe: $(CC) $(CFLAGS) $&.c .c.obj: $(CC) $(CFLAGS) /c $&.c .cpp.exe: $(CC) $(CFLAGS) $&.cpp .cpp.obj: $(CC) $(CPPFLAGS) /c $&.cpp .rc.res: $(RC) $(RFLAGS) /r $& .SUFFIXES: .exe .obj .asm .c .res .rc !if !$d(BCEXAMPLEDIR) BCEXAMPLEDIR = $(MAKEDIR)\..\EXAMPLES !endif
Czarna magia? W następnych podrozdziałach będzie mowa o najważniejsze elementach składni pliku makefile.
Aby to co pisze nie było tylko czystą teorią wymyśliłem projekt HelloProject, którego makefile będziemy rozwijać w miarę postępów w nauce. Jak zorganizować sobie miejsce pracy nad tym projektem? Należy utworzyć nowy katalog z nazwą projektu: C:\Hello\ Następnie trzeba tam skopiować wszystkie jego pliki źródłowe:
- Hello.cpp
- klasa.cpp
- klasa.h
- zasoby.rc
- DEF.def
- makefile.mak
Wynikiem tego projektu będzie windowsowy EXE'k: Hello.exe. Nieważne, co będzie robił ten program, to tylko ćwiczenie, resztę pozostawiam Tobie, Twojej wyobraźni i inwencji. MAKE będziemy wywoływać tam, gdzie jest makefile, czyli z katalogu c:\Hello\. Tam też powinna znaleźć się kopia programu MAKE.
Komentarze
edytujW makefile'u tak jak w C++, również możesz używać komentarzy jedno liniowych. Wszystko to co znajdzie się w jednej linii po symbolu hash'a: # MAKE ignoruje.
# to jest komentarz
Zasady ogólne i szczegółowe
edytujZasady regulują wywoływanie narzędzi w makefile'u. Ogólnie rzecz biorąc zasady określają jakie pliki ma przetwarzać MAKE, ewentualnie gdzie one się znajdują, gdzie ma się znaleźć wynik operacji. Ważną kwestią w zrozumieniu zasad jest fakt, że zasada to tylko prawo, za wypełnienie którego odpowiedzialny jesteś Ty. Pamiętaj też że zasada przekazuje tylko informacje dla MAKE, natomiast komendy, które za jej pomocą wywołujesz rządzą się już swoimi prawami. Zarówno przy komendach, jak i przy samych zasadach można używać symbolu \ jeśli zabraknie Ci miejsca w linii:
<txt>\ <dok. txt'u>
Ogólny szablon zasady wygląda tak:
<zasada> <komenda> [<komenda>] [...]
Pamiętaj tylko o przynajmniej jednej spacji przed komendą, aby nie znajdowała się ona tuż przy początku linii - to błąd! Błędem jest też nie zapisanie samej zasady przy początku linii. Jednym słowem musi to wyglądać tak, jak na powyższym szablonie. Zasada zacznie "wchodzić w życie" (wywoływać swoje komendy), jeśli jej pliki zależne (źródłowe) będą różniły się datą i czasem ostatniej modyfikacji z datą plików wynikowych lub jeśli pliki wynikowe w ogóle nie będą istnieć. Np. jeśli chcemy skompilować plik Hello.cpp za pomocą zasad to kompilator zostanie wywołany kiedy Hello.cpp i Hello.obj będą miały inne daty modyfikacji lub jeśli plik Hello.obj nie będzie istniał. Jak widać w tytule tego rozdziału zasady możemy podzielić na dwa typy: ogólne i szczegółowe (implicit & explicit rules).
Zasady ogólne (implicit rules)
edytujZasady te tyczą się wszystkich plików z określonym rozszerzeniem (ewentualnie możesz określić folder, w którym MAKE ma zezwalać na operacje na nich). Zasady te zakładają tylko zmianę rozszerzenia, a nazwy plików pozostawiają. Implicit rule wygląda tak:
[{<lokalizacja1>}].<roz1>[{<loklalizacja2>}].<roz2>: <komenda/y>
gdzie:
- <lokalizacja1>
- lokalizacja/e plików zależnych (źródłowych) (w przypadku gdy jest ich więcej jako separatora używamy średnika:';')
- <roz1>
- rozszerzenie plików źródłowych
- <lokalizacja2>
- lokalizacja/e na dysku plików wynikowych (docelowych) operacji (gdzie mają się one znaleźć) oddzielone jedna od drugiej średnikiem:';'
- <roz2>
- rozszerzenie plików wynikowych
Na przykład:
{C:\src}.cpp{C:\obj}.obj: bcc32 -c c:\src\hello.cpp copy hello.obj c:\obj
Przejdźmy teraz do analizy tych trzech linijek kodu. Pozwoli Ci to lepiej zrozumieć działanie zasad make'a. W tym przypadku zasada zezwala na operacje na plikach *.cpp tylko z katalogu c:\src, a ich wynik w magiczny sposób musi znaleźć się w c:\obj, oczywiście nie da się tego dokonać bez pomocy systemowego słowa kluczowego COPY albo użycia w parametrach wywołania kompilatora opcji -n. Jeśli nie określisz w zasadzie tych lokalizacji, to dozwolone będą operacje na wszystkich plikach na całym Twoim twardzielu. Określając w zasadzie lokalizacje plików źródłowych nie łudź się że zostanie to przekazane komendom które wywołujesz, dlatego podczas kompilacji zapisałem plik hello.cpp wraz jego z lokalizacją, oczywiście nie musiałbym tego robić, gdybyśmy odpalili MAKE z lokalizacji c:\src.
Zasady szczegółowe (explicit rules)
edytujZasady szczegółowe określają z jakich plików źródłowych powstaje plik wynikowy. W tym przypadku musimy podać dokładne nazwy plików a nie jak to było w przypadku zasad ogólnych samego rozszerzenia plików. Spójrzmy na składnię:
<wynik(i)>:[:] [{<lokalizacja>}]<src> <komenda/y>
- <wynik(i)>
- nazwa pliku/ów wynikowego/ych, który ma być wygenerowany
- <lokalizacja>
- lokalizacja/e plików źródłowych oddzielone od siebie średnikami:';'
- <src>
- plik(i) źródłowy/e
Przykład powinien rozwiać wszelkie wątpliwości:
Hello.exe: {C:\obj}Hello.obj klasa.obj ilink32 /aa /Tpe c0w32.obj C:\obj\Hello.obj C:\obj\klasa.obj,\ Hello.exe,,cw32.lib import32.lib,,
Zasada zakłada że Hello.obj i klasa.obj znajdują się tylko i wyłącznie w katalogu c:\obj. Tak jak w zasadach ogólnych tak i tutaj ta lokalizacja nie ma nic wspólnego z komendami - i w tym przypadku musisz podać lokalizacje każdego ze swoich plików źródłowych (oczywiście jeśli znajdują się one tam gdzie makefile nie musisz tego robić). Zmieni się to dopiero po wprowadzeniu do kodu inteligentnych zmiennych które będą potrafiły czerpać informacje z zasad, ale o tym później.
Wszystkie pliki docelowe (wynikowe) z zasad ogólnych powinny być automatycznie plikami źródłowymi w zasadzie szczegółowej. Inaczej MAKE danej zasady ogólnej nie weźmie pod uwagę. Pokaże to na przykładzie:
.cpp.obj: bcc32 -c Hello.cpp .rc.res: # MAKE nie zwraca na tę zasadę uwagi, bo nie dodałeś brcc32 zasoby.rc # pliku zasoby.res do src zasady szczegółowej Hello.exe: Hello.obj ilink32 /aa /Tpe c0w32.obj Hello.obj klasa.obj,Hello.exe,,\ cw32.lib import32.lib,,zasoby.res
W tym przypadku konsolidator zgłosi błąd, że nie może znaleźć pliku zasoby.res, nic dziwnego - BRCC32 wcale nie został wywołany.
MAKE zakłada, iż zawsze dysponujemy tylko jednym plikiem wynikowym, ale i temu można zaradzić tworząc symboliczne pliki wynikowe o tak:
ALL: <PlikWynikowy1> <PlikWynikowy2> [<Plikwynikowy3> [...]]
Teraz nic nie stoi na przeszkodzie, aby wcześniejszy niefortunny kod zamienić na poniższy:
ALL: zasoby.res Hello.exe .cpp.obj: bcc32 -c Hello.cpp zasoby.res: zasoby.rc brcc32 zasoby.rc Hello.exe: Hello.obj ilink32 /aa /Tpe c0w32.obj Hello.obj klasa.obj,Hello.exe,,\ cw32.lib import32.lib,,zasoby.res
Oczywiście to już od Ciebie zależy jakich zasad wolisz używać. Moim zadaniem jednak warto użyć wielu zasad ogólnych, a tylko jedną szczegółową, chyba że musisz w jednym projekcie mieć dwa pliki EXE (lub DLL). Jest też możliwość przypisania wielu zasad do jednego pliku wynikowego używamy wtedy podwójnego dwukropka:
mylib.lib:: obj1.obj obj2.obj tlib mylib +obj1.obj +obj2.obj mylib.lib:: obj3.obj obj4.obj implib mylib +lib3.obj +lib4.obj
Najpierw do biblioteki statycznej mylib.lib dodawane są obj1.obj i obj2.obj, następnie w drugiej zasadzie do tej samej biblioteki oddajemy: obj3.obj oraz obj4.obj.
Zasada "bez zasad" :-)
edytujJest jeszcze jeden typ zasad. Właściwie to nie wiem, czy można by nazywać to zasadą, bo nie określa żadnych praw dostępu do plików. Jej składnia jest miej więcej taka:
<nazwa>: <komenda/y>
Przydaje się ona do wywołania narzędzi typu debugger lub TOUCH które nie czynią żadnych zmian w rozszerzeniu pliku zatem nie można ich normalnie wywołać z jakichkolwiek wcześniejszych zasad. Poza tym zasada ta zawsze "wejdzie w życie", gdyż nie mamy tu żadnych plików zależnych ani wynikowych. Pewnym jej ograniczeniem jest to że po zakończeniu jej działań MAKE także kończy. Cóż, nie można mieć wszystkiego ;). Dla przykładu podaje wywołanie programu TOUCH:
TOUCH: cd c:\Hello touch *.cpp *.rc
Komendy systemowe
edytujNa co nam zasady skoro i tak nigdy nie zostaną poparte działaniami? - na nic. Dlatego, aby coś zaczęło działać trzeba to...wywołać :). Jak już pisałem oprócz zwykłych wywołań narzędzi z FCLT można tu też umieszczać komendy systemowe, czy włączać inne program takie jak np. kompilator assemblera, czy swój własny program już po skompilowaniu. Pamiętaj jednak, że komendy nie mogą występować w makefile'u od tak sobie, muszą one zawierać się w jakiejś zasadzie tylko w ten sposób można wywoływać narzędzia przez MAKE, inaczej program (MAKE) zgłosi błąd. Poza tym przy wywołaniu 'czegoś' można użyć następujących przedrostków:
@ | wywołanie danego narzędzia nie ma być wyświetlane w linii poleceń, MAKE zawsze przed zastosowaniem jakiejś komendy najpierw wyświetla na ekranie jej wywołanie, ten przedrostek temu zapobiega |
---|---|
- | kontynuuj działnia nawet jeśli narzędzie zgłosi błędy np. zapis: -bcc32 -c hello.cppsprawi, że nawet jeśli w kodzie źródłowym hello.cpp będzie błąd MAKE nie zakończy procesu budowy projektu. |
-<liczba> | w miejsce <liczba> wpisz liczbę, po zwróceniu której make ma przestać dalej działać (wywoływać); np.: -0bcc32 -c hello.cppda nam zakończenie działania programu gdy BCC32 zakończy działanie kodem wyjścia równym 0 |
Właściwie to już możesz spróbować napisać swój własny, prosty makefile, który zautomatyzuje wywołanie kompilatora, kompilatora zasobów i konsolidatora.
#---------------------------- # (c) 2003 by Karol Ossowski #---------------------------- # >> Hello Project << # #---------------------------- .cpp.obj: BCC32 -tW -c -q -w-par -w-rvl Hello.cpp klasa.cpp .rc.res: BRCC32 zasoby.rc Hello.exe: Hello.obj klasa.obj zasoby.res ILINK32 /aa /Tpe /Gk /t /q -w-dup c0w32.obj Hello.obj\ klasa.obj,Hello.exe,,cw32.lib import32.lib,DEF.def,\ zasoby.res
Ten makefile jest dość prymitywny, jednak automatyzuje on już całą budowę projektu HelloProject. Spróbuj go odpalić samemu.
Zmienne
edytujW makefile'u możemy deklarować zmienne gdzie umieścimy pewne łańcuchy znaków, później te "stringi" (z ang. string znaczy łańcuch) będziemy mogli wykorzystać wpisując nazwę zmiennej. Przypisywanie wartości do zmiennej wygląda tak:
<nazwa> = [<łańcuch_znaków>]
Zwroć uwagę, że przypisywany łańcuch nie jest ujęty w "" tak jak to jest w C/C++, wszystko to co znajdzie się za znakiem równości: = w jednej linii zostanie przypisane do zmiennej <nazwa>. np.:
SRC = Hello.cpp klasa.cpp
Wykorzystanie tej zmiennej w makefile'u będzie za to przypominać języki skryptowe takie jak Perl czy Bash:
.cpp.obj: bcc32 -c $(SRC)
I tak każda zmienna przez Ciebie zadeklarowane będzie poprzedzane '$' i ujęte w nawiasy '()', ale analizując te dwa przykłady już możesz odkryć pożyteczność tego wynalazku: deklarując na początku zmienną SRC nie musisz pamiętać już wszystkich plików źródłowych, które wykorzystasz w swoim programie, nawet jeśli projekt się rozrośnie nie będziesz musiał dopisywać w każdym wywołaniu korzystającym ze źródeł jeszcze jednego pliku, ale wystarczy, że na samym początku dopiszesz jeszcze jeden plik *.cpp, a zmienna będzie wtedy wskazywała już na dwa pliki źródłowe.
Jak już pisałem zmienne to po prostu łańcuchy znaków, więc możemy przypisać im również opcje, jakie chcemy wykorzystać przy wywoływaniu narzędzi:
C_FLAGS = -tW -c -v -w-par -w-rvl
Teraz wywołanie BCC32 będzie w makefile'u wyglądać tak:
.cpp.obj: bcc32 $(C_FLAGS) $(SRC)
Zmiana wartości zmiennej
edytujW każdym zapamiętanym w zmiennej "stringu" można dokonać zamiany "podstringów". Robimy to korzystając z następującego szablonu:
$(<nazwa_zm>:<stary_str>=<nowy_str>)
Na przykład można to wykorzystać do zmiany rozszerzenia plików z jakich korzystamy:
SRC = Hello.cpp klasa.cpp $(SRC:.cpp=.obj)
Teraz nie musisz pisać już dodatkowej zmiennej z wynikami konslolidacji - zmienna $(SRC) możemy wykorzystać do wywołania kompilatora, a do wywołania konsolidatora - $(SRC:.cpp=.obj).
Specjalne zmienne
edytujIstnieją jeszcze zmienne specjalne, które są już stworzone przez MAKE i możesz je używać w komendach tuż po zasadach. Są one bardzo wygodne:
zmienna | przekazywane dane w zasadach ogólnych | przekazywane dane w zasadach szczegółowych |
---|---|---|
$* | plik źródłowy (bez rozszerzenia) z lokalizacją | plik wynikowy (bez rozszerzenia) z lokalizacją |
$< | plik źródłowy+rozszerzenie z lokalizacją | plik wynikowy+rozszerzenie z lokalizacją |
$: | lokalizacja plików źródłowych | lokalizacja pliku wynikowego |
$. | plik źródłowy+rozszerzenie | plik wynikowy+rozszerzenie |
$& | plik źródłowy | plik wynikowy |
$@ | plik wynikowy+rozszerzenie z lokalizacją | to samo co $< |
$** | to samo co $< | wszystkie pliki źródłowe+rozszerzenie |
$? | to samo co $< | stare pliki źródłowe |
Przyznajcie, że zmieniają one wręcz filozofie zasad, które stają się nie tylko prawami dostępu do plików, ale i źródłem informacji dla komend np. o lokalizacji plików. Przykład użycia specjalnych makr:
{c:\cpp}.cpp.obj: bcc32 -q -c $<
ten kod spowoduje kompilacje wszystkich plików *.cpp w katalogu c:\cpp, a pliki *.obj, które wygeneruje umieści w lokalizacji w której znajduje makefile. To nie wszystko - zmiennym specjalnym można też zmieniać wartości stosując specjalne znaczniki. Znaczniki te są najczęściej używane przy zmiennych: $@ i $<. Dodanie takiego znacznika modyfikującego wartość danej zmiennej powinno wyglądać tak:
$(<znak specjalnego makra>:[<znacznik>])
Oto typy znaczników do standardowych makr MAKE'a:
znacznik | przekazywane dane o pliku | przykład użycia | przykładowy rezultat dla pliku C:\Proj\kod.cpp |
---|---|---|---|
D | lokalizacja | $(<D) | C:\Proj\ |
F | nazwa i rozszerzenie | $(@F) | kod.cpp |
B | nazwa | $(<B) | kod |
R | lokalizacja,nazwa | $(@R) | C:\Proj\kod |
i jeszcze przykład:
{c:\cpp}.cpp{c:\obj}.obj: bcc32 -q -c -n$(@D) $<
ten fragment skryptu spowodowałby kompilację plików *.cpp z katalogu c:\cpp i zapisywałby pliki *.obj do katalogu c:\obj.
Twoje zmienne powinny być w szczególności używane przy zasadach jakie będziesz stawiał, a zmienne specjalne w komendach np.:
$(BIN): $(SRC:.cpp=.obj) ILINK32 $(L_FLAGS) c0w32.obj $**,$.,,cw32.lib import32.lib $(LIB),$(DEF),$(RES)
Używając zmiennych $** lub $? w zasadzie szczegółowej (explicit rule) można do komendy dodać przedrostek &, który będzie oznaczał, że komenda odnosi się tylko do jednego pliku z pola <src> (normalnie makra $** i $? w zasadach szczegółowych oznaczają wszystkie pliki źródłowe, co uniemożliwi pracę niektórych komend) np.:
hello.exe: hello.obj klasa.obj copy $** c:\obj
Po takiej operacji komenda systemowa COPY zgłosi błąd, ponieważ $** wskazuje na pliki hello.obj i klasa.obj, a COPY może obsługiwać tylko jeden plik. Ten błąd da się naprawić:
hello.exe: hello.obj klasa.obj © $** c:\obj
Teraz system wykona dwie operacje zamiast jednej:
copy hello.obj c:\obj copy klasa.obj c:\obj
..aha przed przystąpieniem do kolejnego rozdziału rozwiniemy nasz HelloProject o obsługe zmiennych. Aby zachować pewien porządek w katalogu c:\Hello najpierw stworzymy następujące podkatalogi,a w nich zawrzemy pliki do projektu HelloProject:
.\src\ | <-- Hello.cpp klasa.cpp |
.\src\hdr\ | <-- klasa.h |
.\src\res\ | <-- zasoby.res |
.\obj\ | <-- makefile.mak DEF.def make.exe *.obj *.li? |
.\bin\ |
Teraz proszę zerknąć na ten kod:
#---------------------------- # (c) 2003 by Karol Ossowski #---------------------------- # >> Hello Project << # #---------------------------- DIR = c:\Hello C_FLAGS = -tW -c -q -w-par -w-rvl L_FLAGS = /aa /Tpe /Gk /t /q -w-dup SRC = Hello.cpp klasa.cpp HDR = klasa.h # linijka odana dla porządku ;) RES = zasoby.res DEF = DEF.def LIB = BIN = Hello.exe {$(DIR)\src}.cpp{$(DIR)\obj}.obj: BCC32 $(C_FLAGS) -I$(DIR)\src\hdr $< {$(DIR)\src\res}.rc{$(DIR)\obj}.res: BRCC32 $< @COPY $:$(@F) $(DIR)\obj @DEL $:$(@F) $(BIN): {$(DIR)\obj}$(SRC:.cpp=.obj) $(RES) ILINK32 $(L_FLAGS) c0w32.obj $(SRC:.cpp=.obj),$.,,cw32.lib\ import32.lib $(LIB),$(DEF),$(RES) COPY $. $(DIR)\bin DEL $.
Jak widać makefile bardzo zyskał na funkcjonalności i co najważniejsze na uniwersalności: dzięki zmiennym cały czas możesz zmieniać skład plików projektu zasadniczo nie ingerując w zasady. Nawet jeśli w projekcie nie przewidujesz zasobów to nie będzie tragedii: operacja kompilacji zasobów nie będzie miała miejsca a plik nie zostanie dodany przez konsolidator. Poza tym zmienna DIR nie została stworzone bez powodu. Dzieki niej możliwa jest także budowa nowego, całkowicie innego projektu, którego wynikiem będzie $(BIN), plikami źródłowymi $(SRC) itd. Oczywiście przy tej operacji trzeba zachować tę samą strukturę podkatalogów projektu i rozmieszczenia w nich typów plików.
Teraz małe sprostowanie tak dziwnego zapisu kompilacji zasobów: BRCC32 nie zachowuje się jak większość programów z FCLT ponieważ zostawia plik wynikowy w lokalizacji ze źródłami.
Jest pewien mankament tego makefile'a: jeśli nie masz plików zależnych zasady szczegółowej MAKE się o nie upomni, choć mają one dopiero powstać w trakcie trwania makefile'a. Jest na to rada: przed pierwszym (później nie ma już takiej potrzeby) uruchomieniem makefile'a "dotknij" programem TOUCH wszystkie pliki źródłowe. Np. w tym projekcie będzie to wyglądać tak:
cd c:\Hello\obj touch zasoby.res klasa.obj Hello.obj cd c:\Hello\src touch *.cpp cd .\res touch *.rc
Można rozwiązać to w ten sposób... ale jest znacznie bardziej funkcjonalna dyrektywa...
Dyrektywy
edytujDyrektyw/słów kluczowych w borlandowskim makefile'u jest mniej niż np. w C++ ale wystarczająco dużo, aby efektywnie zarządzać projektem. W tym przypadku spisałem niemal wszystkie wyrażenia (chyba że sam któregoś nie rozumiałem). Nie musisz się ich uczyć na pamięć, wystarczy, że będziesz miał te 5 tabelek zawsze pod ręką. Warto jednak na początek je przeczytać, żeby przekonać się jakie są możliwości słów kluczowych w MAKE. Ważną zasadą przy wykorzystywaniu dyrektyw jest to aby MAKE nie pomylił danej dyrektywy z wywołaniem programu. Jeśli więc chcemy dyrektywa umieścić w polu komend jakiejś zasady to trzeba ją postawić na początku wiersza w innym przypadku zostanie ona potraktowana jako komenda systemowa, a to może oznaczać tylko kłopoty...
Pliki projektu i konfiguracja MAKE
edytujnazwa | wiersz poleceń | opis |
---|---|---|
.autodepend | -a | sprawdzaj pliki nagłówkowe przed kompilacją i jeśli zostaną zmodyfikowane kompiluj jeszcze raz pliki które z nich korzystają |
.noautodepend | -a- | nie sprawdzaj plików nagłówkowych |
.cacheautodepend | -c | przechowuj w pamięci podręcznej pliki wchodzące w skład projektu i jeśli nie zostaną w poczynione żadne zmiany nie wykonuj na nich operacji ponownie |
.nocacheautodepend | -c- | nie przechowuj w pamięci podręcznej plików wchodzących w skład projektu |
.keep | -K | zachowuj pliki tymczasowe tworzone podczas działania programu MAKE |
.nokeep | -K- | nie przechowuj plików tymczasowych które są tworzone podczas działania MAKE'a |
.ignore | -i | ignoruj wartość jaką zwróci komenda |
.noIgnore | -i- | nie ignoruj wartości jaką zwróci komenda |
.silent | -s | nie pokazuj na ekranie wywołania narzedzia |
.nosilent | -s- | pokazuj na ekranie wywołanie narzędzia lub komendę jaką wykonuje system |
.swap | -S | wyczyść swoją pamięć zanim zaczniesz wywoływać narzędzia (ta instrukcja jest dobra podczas operacji na dużych plikach) |
.noswap | -S- | nie czyść pamięci przed wywoływaniem narzędzi |
-r | ignoruje zasady jakie podaje nam standardowy plik makefile BUILTINS.mak znajdujący się w katalogu BIN kompilatora |
szablon | opis |
---|---|
.precious: <PlikWynikowy> | jeśli któryś z programów "padnie" MAKE wyrzuca swój plik wynikowy. ta komenda temu zapobiega |
.suffixes: .<roz1> [.<roz2> [.<roz3>]..] | twórz pliki najpierw z rozszerzeniem: <roz1> później z <roz2>, a na końcu z <roz3> (itd.); ta dyrektywa jest analizowana przez zasady ogólne i określa ona porządek tworzenia PlikówDocelowych |
.path.<roz> = <lokalizacja> | szukaj pliku z rozszerzeniem <roz> w podanej <lokalizacji> (ta dyrektywa niweluje problem z wcześniejszym makefile'em) |
!include [<lokalizacja>]<Nazwapliku> | dodaj tekst do obecnego makefile'a z pliku <NazwaPliku> (działa jak makro-instrukcja #include w C/C++) |
!undef <nazwa_zmiennej> | "wyrzuć" zmienną <nazwa_zmiennej> |
Instrukcje warunkowe
edytujszablon | opis |
---|---|
!ifdef <nazwa_zmiennej> <operacje> | jeśli zmienna <nazwa_zmiennej> jest zadeklarowana wykonaj <operacje> |
!ifndef <nazwa_zmiennej> <operacje> | jeśli zmienna <nazwa_zmiennej> nie jest zadeklarowane wykonaj <operacje> |
!if <warunek> <operacje> | jeśli <warunek> zostanie spełniony wykonaj <operacje> |
!else <operacje> | w przeciwnym wypadku wykonaj <operacje> (musi występować z !if lub !ifdef lub !ifndef) |
!elif <warunek> <operacje> | w przeciwnym wypadku, jeśli <warunek> jest spełniony wykonaj <operacje> (musi występować z !if lub !ifdef, !ifndef, !else) |
!endif | kończy instrukcję warunkową |
Instrukcję warunkową warto bardziej wnikliwie zanalizować, ponieważ jak sądzę będziesz ją często używał(a).
- Każda instrukcja warunkowa musi się kończyć dyrektywą !endif
- Przy określaniu warunków instrukcji warunkowej możemy użyć między innymi następujących operatorów:
operator opis operator opis - negacja || logiczne OR + dodawanie ! logiczne NOT - odejmowanie && logiczne AND * mnożenie >= większe lub równe / dzielenie <= mniejsze lub równe == równe > większe != nierówne < mniejsze
- Aby sprawdzić czy zmienna jest zadeklarowana (tj. czy je wcześniej stworzyliśmy) można użyć specjalnego znacznika d. Zastosowanie go jest równoważne z użyciem dyrektywy !ifdef:
- !ifdef <zmienna> to, to samo co !if $d(<zmienna>)
- !ifndef <zmienna> to, to samo co !if !$d(<zmienna>)
Ekran
edytujszablon | opis | rezultat |
---|---|---|
!error <komunikat> | polecenie, po natrafieniu na które MAKE kończy działanie i wyświetla na ekranie rezultat... | Fatal makefile <numer_linii>: Error directive: <komunikat> |
!message <komunikat> | jeśli MAKE natrafi na to polecenie, wyświetla na ekranie rezultat... | <komunikat> |
W obu dyrektywach "ekranowych" można używać zmiennych do reprezentacji komunikatów.
MAKE w praktyce
edytujTeraz pora na wzbogacenie naszego makefile'a o dyrektywy i tym samym doprowadzenie skryptu do ostatecznej wersji. Przeanalizuj ten kod, a na pewno rozjaśni Ci się w głowie, o czym była mowa przez ostatnie 25kB :). Do tego projektu potrzebujesz jeszcze jeden plik: HelloProject.txt który jako jedyny powinien się znaleźć w katalogu głównym projektu. Nasz ostateczny makefile będzie się bowiem składał z dwóch plików pierwszy to przed chwilą utworzony HelloProject.txt, a drugi to wszystkim znany makefile.mak (połączone są one dyrektywą !include). Dodatkowo w katalogu c:\Hello\src powinieneś utworzyć jeszcze jeden folder i zamieścić tam pliki *.asm:
.\asm <-- ewentualne wstawki asmowe (*.asm)
Teraz możesz przejść do analizy.
c:\Hello\HelloProject.txt:
#---------------------------- # (c) 2004 by Karol Ossowski #---------------------------- # >> Hello Project << # #---------------------------- #Tryb budowy projektu: # 1 - Release # 2 - Debug # 3 - Rebuilt TRYB = 1 SRC = Hello.cpp klasa.cpp #nie wstawiaj przecinków HDR = klasa.h ASM = RES = zasoby.res LIB = BIN = Hello.exe DEF = DEF.def C_FLAGS = -tW -c -q -w-par -w-rvl L_FLAGS = /aa /Tpe /Gk /t /q -w-dup
c:\Hello\obj\makefile.mak:
DIR = c:\Hello #nie wstawiaj na końcu '\' !include $(DIR)\HelloProject.txt .silent .autodepend .cacheautodepend .suffixes: .cpp .asm .rc .res .obj .exe .path.obj = $(DIR)\obj .path.res = $(DIR)\obj .path.def = $(DIR)\obj !if $(TRYB)== 2 DEBUG=-v !message_________.:Tryb DEBUG:._________ !elif $(TRYB)== 1 !message_________.:Tryb RELEASE:._________ !elif $(TRYB)== 3 !message odbudowywanie.... REBUILT: TOUCH $(DIR)\src\*.cpp TOUCH $(DIR)\src\res\*.rc TOUCH $(DIR)\src\asm\*.asm DEL $(DIR)\obj\*.il* !else !error NIE wybrano trybu budowy projektu !endif #Zasoby# {$(DIR)\src\res}.rc{$(DIR)\obj}.RES: BRCC32 $< COPY $:$(@F) $(DIR)\obj#kopiowanie plików wynikowych do \obj DEL $:$(@F) #likwidowanie powyższych plików w \src #Asembler# {$(DIR)\src\asm}.asm{$(DIR)\obj}.obj: TASM32 $< #Kompilacja# {$(DIR)\src}.cpp{$(DIR)\obj}.obj: BCC32 $(C_FLAGS) $(DEBUG) -I$(DIR)\src\hdr $< #Konsolidacja# $(BIN): $(SRC:.cpp=.obj) $(ASM:.asm=.obj) $(RES) ILINK32 $(L_FLAGS) $(DEBUG) c0w32.obj $(SRC:.cpp=.obj)\ $(ASM:.asm=.obj),$.,,cw32.lib import32.lib $(LIB),$(DEF),$(RES) !if $(TRYB)==2 TD32 $(BIN) !else COPY $. $(DIR)\bin $(DIR)\bin\$(BIN) !endif
Przyznam, że ten makefile jest dość zaawansowany jak na materiał dla świeżo upieczonych użytkowników MAKE'a ale chciałem pokazać jak najwięcej możliwości tego narzędzia. Kod ten składa się z dwóch części:
- interfejs (HelloProject.txt) - miejsce, które będziesz stale używał(a) w czasie pracy nad projektem. Możesz deklarować tam nowe pliki źródłowe, odznaczać opcje linkera i kompilatora, i określać tryb budowy projektu;
- implementacja (makefile.mak) - część, w której zawarte są same komendy budowy projektu, ten segment jest tworzony tylko raz podczas pisania makefile'a i od tego czasu raczej nie powinieneś go ruszać;
Tak naprawdę skrypt ten jest na tyle uniwersalny, że możesz używać go w wielu projektach (byle była zachowana odpowiednia struktura katalogów). Do makefile'a dodałem zasadę kompilującą w wstawki assemblerowe. Program TASM32, który jest kompilatorem assemblera niestety nie należy do FCLT, to wciąż komercyjny produkt Borlanda. Opcję tą dodałem tylko po to, aby makefile był już w 100% kompletny. W kodzie użyłem jeszcze jednego programu spoza FCLT. Jest to Turbo Debugger (TD32).TD32 to świetny darmowy debugger który powinieneś mieć w swojej kolekcji, jest do ściągnięcia z ze internetowej Borlanda.