make, cmake
make¶
Nagyobb rendszerek fordításakor segítség, ha a fordítás lépéseit, elemeit nem kell mindig egyesével megadnunk, hanem egy egyszerűbb eszköz segítségével definiálhatjuk az egyes lépéseket, ráadásul mindezt úgy, hogy a rendszer azon részei legyenek mindig újrafordítva, amelyben valamilyen változtatás történt a legutóbbi fordítás óta.
Ezt nyújtja nekünk a Makefile
a C/C++ programok fordításához, amely gyakorlatilag egy szabálygyűjteményt kezel, amelyeket végrehajtva akár egy teljes fordítási folyamatot is le tudunk vezényelni.
Szabályok¶
-
Szabály (rule): célok (targets), előfeltételek (prerequisites) & leírások (recipes). Minden szabály több parancs végrehajtásából állhat, minden parancs külön sorba kell kerüljön.
-
Target: ami elkészül (általában egy fájl, amit a program generál), prereq: miből, recipe: hogyan.
-
Hívása:
make TARGET
(amennyiben nincs TARGET, akkor az első szabály fog meghívódni). -
Adott leírás végrehajtódik, ha a cél még nem létezik, vagy ha valamelyik előfeltétel menet közben megváltozott.
-
clean
: speciális target, hatása nem egyclean
nevű állomány elkészítése, hanem a megfelelő állományok törlése. -
Vigyázat!
Makefile
-oknál a formátum elég régimódi: kötelezően tabot kell használni (8 space nem ugyanazt jelenti)
Példaként legyen adott egy hello.c
állományunk:
1 2 3 4 5 6 |
|
A fordításához szükséges Makefile
1 2 3 4 5 |
|
Ebben a Makefile
-ban egy hello
és egy clean
target van. Előbbi célja a hello
bináris állomány előállítása, utóbbié ennek törlése. A hello
target létrehozása a hello.c állomány meglététől függ. Ha még nincs hello
binárisunk, akkor a make
parancs kiadásával meghívódik ez a target, és előáll a bináris a gcc
megfelelő hívásával. Amennyiben már lenne hello
bináris, de annak az elkészítési ideje régebbi, mint a hello.c
állomány -amitől függ a target végrehatjása - utolsó módosításai ideje, a make
meghívásával újrafordítódik a bináris.
A clean
targetet külön kell hívnunk, ha azt szeretnénk, hogy a bináris futtatása után az törlődjön:
1 2 3 |
|
Ahogy nő a Makefile
mérete, elképzelhető, hogy megváltozik annak default szabálya. Hogy ezt elkerüljük, általános elv, hogy alkalmazunk egy all
targetet a Makefile
elején, ebben rögzítve, hogy mely fájl/fájlok, avagy targetek elkészítése a végső cél.
Megjegyzés: a make
parancs alapvetően a Makefile
-t értelmezi, és annak tartalmától függ, mi is történik. Ha más fájlt szeretnénk a make
paranccsal feldolgozni, használjuk a -f
kapcsolót, majd adjuk meg a fájlnevet.
Implicit szabályok¶
A Makefile
-okat alapvetően a C/C++ programok fordítására találták ki (mégha mást is meg lehet oldani velük), ezért vannak olyan ismeretei, amelyek a C/C++ programok fordítását segítik, és anélkül, hogy konkrétan megmondanánk, mit kell tenni, a make
tudja azt. Ilyen implicit szabályok lehetnek:
n.o
állomány automatikusan előáll egyn.c
állományból a$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@
paranccsal (azaz ha a target%.o
, az előfeltétel%.c
, akkor ki sem kell írni a parancsot)n.o
állomány automatikusan előáll egyn.cc
vagyn.cpp
állományból a$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
n
állomány előáll azn.o
automatikus linkelésével a$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
parancsot futtatva
.PHONY¶
Ha nem szeretnénk, hogy a target nevének függvénye, illetve az, hogy létezik-e ilyen nevű állomány, vagy se legyen az előfeltétele az adott target lefuttatásának, akkor az adott targetet soroljuk fel a .PHONY
target előfeltételeként:
1 2 3 |
|
Általában a clean
és all
targeteket érdemes megadni ilyen módon, hiszen ezek bizonyos értelemben ál targetek, a nevük nem tükrözi a feladatukat, és végrehajtásuk hatására nem jön létre a nevükkel megegyező nevű állomány.
Változók¶
Elképzelhető, hogy egy-egy target ugyanattól, vagy ugyanazoktől az előfeltételektől függ. Például attól, hogy elkészültek-e az adott rendszer különböző moduljai, object állományai. Ilyenkor ahelyett, hogy mindig felsorolnánk ezeket, érdemes változókat használni, amelyeknél elegendő egyszer felsorolni a szükséges előfeltételeket, és utána erre a változóra hivatkozhatunk minden alkalommal.
1 2 3 4 5 6 7 8 9 |
|
Széles körben használt, standardnak tekinthető változók:
- $(CC)
: a rendszer default fordítója
- $(CFLAGS)
: parancssori kapcsolók, amiket a fordítónak átadunk C fordításkor
- $(LDFLAGS)
: linkelés parancssori kapcsolói
- Az $(LDFLAGS)
használata nem szükségszerűen fontos egy ilyen egyszerű példában, de jó gyakorlatként érdemes hosszászokni:
1 2 3 |
|
Automatikus változók:
$@
- a target neve$<
- az első előfeltétel$^
- az össszes előfeltétel$?
- az összes előfeltétel, amely újabb, mint a target
Wildcardok:
-
%
-
illeszkedésnél egy sztring egy vagy több karakterére illeszkedhet
- helyettesítéskor az erre illeszkedő karaktersorozatot tudjuk kicserélni
-
legtöbbször szabály definíciókban vagy néhány speciális függvénynél használjuk
-
*
-
hasonló, mint az előbbi, de vigyázni kell vele, mert ha nem illeszkedik semmire, akkor marad a jelentése
*
- ha mindenképp wildcardként szeretnénk kezelni, akkor használni kell a
wildcard
függvényt$(wildcard *.o)
%.o: %.c
önmagában egy-egy jó minta fordításhoz, ugyanakkor a header állományokban levő változtatásokat nem érzékeli. Manuálisan azt vezetni, hogy melyik forrás állomány melyik headertől függ (tranzitíven), nagy rendszerek esetében hibákat hordozhat magában.
- A fordítót meg lehet arra kérni, hogy gyűjtse össze az adott forrásban használt header állományokat, és azokat írja ki egy speciális
Makefile
formátumban. Általában ennek a fájlnak a kiterjesztése.d
, minden egyes forrásra egy külön ilyen fájl. include
direktíva: másolhat/beilleszthet tartalmakat másMakefile
-okból (például ilyen.d
állományokból. A lenti példában az ezzel a módszerrel megadott megoldás a függőségek kezelésére egy kicsit egyszerűbbé válik.
hello.c:
1 2 3 4 5 |
|
hello.h:
1 2 3 4 5 6 |
|
main.c:
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Függvények¶
Már láttunk példát pár függvényre, amelyek meghívhatóak $(fn, arguments)
vagy ${fn, arguments}
alakban.
Néhány függvény a teljesség igénye nélkül:
$(subst from,to,text)
: sztring helyettesítés, ahol atext
paraméterben előforduló összesfrom
kifejezésto
-ra íródik.$(patsubst pattern,replacement,text)
: atext
whitespace-szel szeparált szavaiban apattern
-re illeszkedő szavakat keres, amelyet lecserél areplacement
által adott minta alapján.$(strip string)
: a sztringet határoló whitespace karaktereket eltávolítja$(sort list)
: alist
-ben adott szavakat rendezi$(dir names)
: anames
-ben felsorolt fájlok könyvtár részét adja vissza- ...
CMake¶
Nagyobb projektek esetén a Makefile kézi karbantartása könnyen átláthatatlanná válik, különösen ha több platformon vagy többféle konfigurációval szeretnénk fordítani a programot. Erre nyújt megoldást a CMake, amely egy platformfüggetlen build rendszer-generátor. A CMake nem fordít közvetlenül, hanem Makefile-t, ninja-t vagy egyéb build scriptet generál, amely aztán fordításra használható.
A CMake alapja egy konfigurációs fájl, amelyet mindig CMakeLists.txt néven kell elnevezni. Ebben határozzuk meg:
- a projekt nevét,
- a verziót,
- a fordítandó forrásokat,
- a fordítókapcsolókat,
- a szükséges könyvtárakat.
Példa CMakeLists.txt¶
Használjuk az előbbi hello.c, main.c és hello.h forrásállományokat a példához!
1 2 3 |
|
CMake használata¶
Ha már rendelkezünk egy CMakeLists.txt
fájllal, a következő lépésekkel tudunk fordítani:
1 2 3 4 |
|
- A
cmake ..
parancs legenerálja a Makefile-okat a build könyvtárban (természetesen ha aCMakeLists.txt
elérési útvonala nem a gyökér könyvtár, akkor azt módosítani kell). - A
make
utána már használható a projekt lefordítására.
Előnyök¶
- Platformfüggetlenség: nem csak GNU Make-ot, hanem Visual Studio projektet, ninja-t, stb. is képes generálni.
- Könnyű konfiguráció többféle build-típusra (debug, release, stb.).
- Kódmodulok, könyvtárak és külső függőségek egyszerű kezelése.
- Automatikus header dependency kezelés (nincs szükség
.d
fájlokra külön).
Továbbfejlesztés¶
A CMake sokkal többre képes:
- különböző build konfigurációk beállítása (
CMAKE_BUILD_TYPE
), - tesztelési keretrendszerek integrációja,
- külső könyvtárak
find_package
-gel történő kezelése, - telepítő készítése.