MAKE Uvod ⇀ Šta je GNU Make? ⇀ Kada je pogodno, a kada nije pogodno koristiti Make? ⇀ Kako instalirati Make? ⇀ Pozivanje Make-a ⇀ Komponente Makefile-a Promenljive ⇀ Davanje imena promenljivama ⇀ Automatske promenljive ⇀ Implicitne promenljive ⇀ Operatori dodele i proširivanja ⇀ Referenca zamene ⇀ Nadjačavanje promenljivih i override direktiva ⇀ Višelinijske promenljive i define direktiva ⇀ Promenljive okruženja ⇀ Promenljive specifične za cilj ⇀ Promenljive specifične za obrazac ⇀ Brisanje vrednosti promenljivih i undefine direktiva Pravila ⇀ Sintaksa pravila ⇀ Korišćenje džokera u pravilima i funkcija wildcard ⇀ Pravila bez preduslova ⇀ Implicitna pravila Ciljevi ⇀ Ugrađena imena ciljeva ⇀ Lažni ciljevi ⇀ Prazne ciljne datoteke ⇀ Automatsko generisanje ciljeva Višestruki ciljevi ⇀ Pravila sa nezavisnim ciljevima ⇀ Pravila sa grupisanim ciljevima Preduslovi ⇀ Normalni preduslovi ⇀ Preduslovi sa redosledom Pretrazivanje preduslova u direktorijumima ⇀ Opšta pretraga i VPATH ⇀ Selektivna pretraga i vpath direktiva Komande ⇀ Sintaksa komandi ⇀ Izvršavanje ⇀ Greške Funkcije ⇀ Sintaksa za pozivanje funkcija ⇀ Funkcije za zamenu i analizu stringova ⇀ Funkcije za imena datoteka ⇀ Funkcije za uslove ⇀ Funkcija foreach ⇀ Funkcija datoteke ⇀ Funkcija poziva ⇀ Funkcija vrednosti ⇀ Funkcija eval ⇀ Funkcija porekla ⇀ Funkcija ukusa ⇀ Funkcije koje kontrolišu Make Uslovi ⇀ Primer ⇀ Sintaksa uslova ⇀ Uslovne direktive Primer 1 ⇀ Kompilacija u jednom koraku ⇀ Kompilacija i linkovanje ⇀ Lažni ciljevi ⇀ Automatke promenljive Primer 2 Primer 3





MAKE

Većina programera se susreće sa GNU Make-om u nekom trenutku svoje karijere, bilo da ga koriste za kompajliranje malih biblioteka ili pravljenje čitavog projekta. Iako postoji mnogo, mnogo alternativa Make-u, on se i dalje često bira kao sistem za izradu novog softvera s obzirom na njegov skup funkcionalnosti i široku podršku. U ovom poglavlju upoznaćemo se sa opštim konceptima i karakteristikama GNU Make-a i naučiti kako da ih na najbolji način iskoristimo.

Uvod

Šta je GNU Make?

Shell komande za prevođenje i povezivanje programa mogu biti kompleksne i mnogobrojne. Make automatizuje složene aspekte kreiranja izvršnog fajla od izvornog koda. Obično se koristi za transformaciju datoteka u neki drugi oblik, npr. kompajliranje datoteka izvornog koda u programe ili biblioteke. To radi praćenjem preduslova i izvršavanjem hijerarhije komandi za proizvodnju ciljeva.

Kada je pogodno, a kada nije pogodno koristiti Make?

Make je pogodan za pravljenje manjih C/C++ projekata ili biblioteka koje bi bile ukljucene u neki drugi projekat.
Za veće projekte postoje moderniji alati jednostavniji za rad. U sledećim situacijama bilo bi pogodnije izabrati CMake, Bazel ili Mesol:
  ⇀ Kada broj fajlova iznosi nekoliko stotina.
  ⇀ Kada je poželjan korak konfigurisanja.
  ⇀ Kada projekat ostaje privatan i kada krajnji korisnik ne mora da ga gradi.
  ⇀ Kada je otklanjanje grešaka prenaporno.
  ⇀ Kada je potrebno da verzija bude višeplatforma, tj. podržana na macOS, Linux i Windows sistemima.

Kako instalirati Make?

Podrazumevani Ubuntu repozitorijumi sadrže meta-paket pod nazivom build-essential koji uključuje različite razvojne biblioteke i alate potrebne za kompajliranje softvera (GNU kompajler, GNU debager, …).
Da biste instalirali pakete razvojnih alata, pokrenite sledeću komandu kao root ili korisnik sa sudo privilegijama:
$ sudo apt update
$ sudo apt install build-essential

Komanda instalira dosta paketa među kojima su gcc, g++ i make.
Dokumentaciju o Make-u možete dobiti pomoću sledećih naredbi:
$ man make
$ make -help

Pozivanje Make-a

Pokretanje make iz terminala će tražiti datoteku pod nazivom GNUmakefile, makefile ili Makefile, tim redom, iz trenutnog direktorijuma i pokušati da ažurira podrazumevani cilj.
Određeni makefile možete navesti sa -f/--file argumentom:
$ make  -f   foo.mk $ make  -file  foo.mk
Make-u možete proslediti direktorijum sa -C argumentom:
$ make  -C   some/sub/directory
Takođe, Make može i paralelno da pokreće poslove ako zadate opcije -j ili -l. Ograničenje posla potrebno je postaviti na 1,5 puta veći broj od broja procesorskih jezgara koje imate:
# računar ima 4 jezgra $ make  -j   6
Postoji nekoliko načina da programski pronađete broj CPU za trenutnu mašinu. Jedna mogućnost je da koristite funkciju python multiprocessing.cpu_count() da biste dobili broj niti koje sistem podržava:
# pozivamo python funkciju za cpu_count() u subshell-u $ make -l $(python -c "import multiprocessing; print(multiprocessing.cpu_count())")
Ako imate mnogo izlaza iz Make komandi paralelno, možda će doći do preplitanja na standardnom izlazu. Ovo rešavamo korišćenjem opcije --output-sync=target:
$ make -j   --output-sync=target 
Ona će odštampati ceo izlaz komande svakog cilja bez umetanja izlaza neke druge komande.

Komponente Makefile-a

Svaki Makefile moze da sadrži:

● Eksplicitno pravilo kaže kada i kako prepraviti jednu ili više datoteka, koje se navode kao preduslovi za cilj. Takođe, može dati pravilo za korišćenje, za kreiranje ili ažuriranje ciljeva.
● Implicitno pravilo kaže kada i kako prepraviti klasu datoteka na osnovu njihovih imena. Opisuje kako cilj može zavisiti od datoteke sa imenom sličnim cilju i daje pravilo za kreiranje ili ažuriranje takvog cilja.
● Definicija promenljive je linija koja navodi vrednost tekstualnog niza za promenljivu koja kasnije može zameniti deo makefile-a.
● Direktiva je instrukcija za make da uradi nešto posebno dok čita makefile. Ovo uključuje:
  ⇀čitanje drugog makefile-a
  ⇀odlučivanje (na osnovu vrednosti promenljivih) da li da koristite ili ignorišete deo makefile-a
  ⇀definisanje promenljive iz doslovnog niza koji sadrži više redova
● Komentar je linija koja počinje simbolom #. Linija koja sadrži samo komentar (sa možda razmacima pre njega) je praktično prazna i ignoriše se. Ako želite literal #, možete ga koristiti u kombinaciji sa obrnutom kosom crtom, \#.

Ne možete koristiti komentare unutar referenci promenljivih ili poziva funkcija jer se tada svaka instanca # tretira doslovno, a ne kao početak komentara !!!

U sledećem primeru možete videti neke od komponenti o kojima će biti više reči u nastavku:
# Ovo je jedan komentar # foo je promenljiva foo = "Hello world!"
# Ovo pravilo kreira cilj "test" koristeći "test.c" kao preduslov test: test.c   # korišćenje promenljive "FOO"   echo $(foo)   # pozivanje C kompajlera korišćenjem promenljive koja predefiniše podrazumevani C compiler u '$(CC)'   $(CC) test.c -o test

Promenljive

Promenljive su imena definisana u makefile-u koja predstavljaju sekvence karaktera jer Make ne sadrži druge tipove podataka. Omogućavaju da se tekstualni niz definiše jednom i koristi eksplicitnom zamenom u pravilima (ciljevima, preduslovima i komandama) i drugim delovima makefile-a. Na taj način promene možete vršiti samo nad promenljivom, a one će biti vidljive na svim mestima u makefile-u na kojima je promenljiva korišćena.

U nekim drugim verzijama Make-a, promenljive se nazivaju makroi.
Promenljive mogu predstavljati liste imena datoteka, opcije za prosleđivanje kompajlerima, programe za pokretanje, direktorijume za traženje izvornih datoteka, direktorijume za pisanje izlaza, itd. Praksa je da svaki makefile sadrži promenljivu sa imenom objects, OBJECTS, objs, OBJS, obj ili OBJ koja sadrži listu svih objektnih fajlova.

Davanje imena promenljivama

Ime promenljive može biti bilo koji niz znakova koji NE sadrži dve tačke (:), tarabu (#), jednako (=) ili blako karakter. Razlikujemo velika i mala slova. Imena foo, FOO i Foo odnose se na različite promenljive. Koriste se na sledeći način:

$ foo
$ FOO
$ Foo
Imena promenljivih koja počinju tačkom (.) i velikim slovom imaće posebna značenja u novijim verzijama Make-a.
Preporuka je da se mala slova koriste za imena promenljivih koja služe internim svrhama u makefile-u, a da se velika slova rezervišu za parametre koji kontrolišu implicitna pravila ili za parametre koje korisnik treba da zameni komandnom opcijom.

Automatske promenljive

Automatske promenljive su specijalne promenljive definisane od strane Make-a čija se imena sastoje od jednog ili nekoliko znakova interpunkcije. Njihove vrednosti računaju se iznova za svako pravilo koje se izvršava, na osnovu cilja i preduslova.

Neke od automatskih promenljivih date su u sledećoj tabeli:

promenljiva vrednost promenljive
$@ ime fajla cilja
$% ime ciljnog člana, kada je cilj član arhive
$* ime datoteke cilja bez ekstenzije
$< ime fajla prvog preduslova
$^ imena svih preduslova bez dupliranja (sa razmakom između njih)
$+ imena svih preduslova sa dupliranja
$? imena svih preduslova novijih od cilja

Pretpostavimo da pišete pravilo za prevođenje .c datoteke u .o datoteku. U komandi ne možete napisati ime izvorne datoteke jer će se ona razlikovati svaki put kada se pravilo primeni. Tada možete koristili $@ za naziv objektne datoteke i $< za naziv izvorne datoteke.

Vrednosti automatskih promenljivih imaju ograničen opseg u kome su dostupne. Ne možete ih koristiti bilo gde u okviru ciljne liste pravila. Tamo nemaju nikakvu vrednost i proširiće se na prazan string. Takođe, ne može im se pristupiti direktno u okviru liste preduslova pravila.

Postoji posebna karakteristika GNU Make-a, sekundarno proširenje, koja omogućava da se vrednosti automatskih promenljivih koriste u listama preduslova.

Implicitne promenljive

Ugrađena implicitna pravila koriste određene unapred definisane promenljive koje nazivamo implicitnim promenljivama. Mogu se podeliti u dve klase:
  ⇀one koje predstavljaju imena programa (poput CC)
  ⇀one koje sadrže argumente za programe (poput CFLAGS)

Ako vrednost promenljive sadrži više od jednog argumenata, razdvojite ih razmacima.
Sledeća tabela opisuju neke od najčešće korišćenih implicitnih promenljivih. Ova lista nije konačna, a podrazumevane vrednosti prikazane ovde možda ne odgovaraju vašem okruženju. Da biste videli kompletnu listu za vašu instancu GNU Make-a, možete pokrenuti:

$ make -p

promenljiva vrednost promenljive
$(CC) C kompajler (cc)
$(CPP) C pretprocesor
$(CXX) C++ kompajler (g++)
$(CFLAGS) dodatne opcije za C kompajler
$(CXXFLAGS) dodatne opcije za C++ kompajler
$(AR) program za arhiviranje (ar)
$(AS) program za asembliranje (as)

Možete da promenite vrednosti ovih promenljivih u makefile-u, sa argumentima koje treba da napravite, ili da u okruženju promenite način na koji implicitna pravila funkcionišu bez redefinisanja samih pravila. Možete da otkažete sve promenljive koje koriste implicitna pravila opcijom -R ili opcijom --no-builtin-variables.

Operatori dodele i proširivanja

Razlikujemo dve vrste promenljivih u zavisnosti od toga kako im se dodeljuje vrednost i kako se pomašaju kada se prošire:
  ⇀rekurzivno proširene - PROMENLJIVA = IZRAZ
Izraz na desnoj strani se doslovce dodeljuje promenljivoj. Ponaša se slično kao
makro u C/C++.

foo = 1 bar = $(foo) foo = 2 # štampa bar=2 $(info bar=$(bar))
  ⇀jednostavno proširene - PROMENLJIVA := IZRAZ

Rezultat izraza sa desne strane dodeljuje se promenljivoj. Izraz se proširuje u vreme dodeljivanja.
foo = 1 bar := $(foo) foo = 2 # Štampa bar=1 $(info bar=$(bar))
Funkcija $(info ...) se koristi za štampanje izraza i može biti korisna prilikom otklanjanja grešaka u makefile-u!
Postoji još jedan operator dodele za promenljive, operator uslovne promenljive (?=). Ima efekat samo ako promenljiva još uvek nije definisana. Sledeći kodovi su ekvivalentni:
foo ?= bar
ifeq ($(origin foo), undefined)   foo = bar endif
Promenljiva postavljena na praznu vrednost je i dalje definisana, tako da ?= neće postaviti tu promenljivu.
Obično je korisno dodati tekst na vrednost promenljive koja je već definisana. Proširivanje promenljive će na nju dodati blanko karakter i novi sadržaj:
foo = jedan foo += dva # foo je sada "jedan dva"
Razmak možete izbeci na sledeći način:
foo = jedan foo = $(foo)dva # foo je sada "jedandva"
Napomena: Promenljive koje nisu eksplicitno, implicitno, niti automatski postavljene, procenjivaće se kao prazan string.

Referenca zamene

Referenca zamene zamenjuje vrednost promenljive sa izmenama koje navedete. Uzima vrednost promenljive i prvi karakter koji ste naveli, a koji se nalazi na kraju reci, i zamenjuje ga drugim karakterom koji ste naveli. Kada kažemo „na kraju reči“, mislimo da se mora pojaviti ili nakon razmaka ili na kraju vrednosti da bi bio zamenjen. Ostala pojavljivanja prvog karaktera u vrednosti ostaju nepromenjena.
Ima sledeći oblik:

$(var:a=b)
${var:a=b}
Uzima vrednost promenljive var i zamenjuje svako a na kraju reči sa b.
Sledeći primer postavlja bar na a.c b.c l.a c.c.

foo := a.o b.o l.a c.o
bar := $(foo:.o=.c)

Referenca zamene je skraćenica za funkciju proširenja patsubst. Naredni kodovi su ekvivalenti:

$(var:a=b)
$(patsubst %a,%b,var)
Druga vrsta reference zamene omogućava da koristite punu snagu patsubst funkcije. Ima isti oblik opisan gore, s tim što sada a mora da sadrži jedan znak %. Sledeći kodovi su ekvivalentni:

$(var:a=b)
$(patsubst a,b,$(var))
Sledeći primer postavlja bar na a.c b.c l.a c.c.

foo := a.o b.o l.a c.o
bar := $(foo:%.o=%.c)

Nadjačavanje promenljivih i override direktiva

Ako argument komandne linije dodeljuje vrednost promenljivoj korišćenjem operatora = ili :=, ignorišu se sve uobičajene dodele iste promenljive u makefile-u. Tada kažemo da su zamenjene argumentom komandne linije. Najčešći način korišćenja ove mogućnosti je prosleđivanje dodatnih opcija kompajlerima.

U pravilno napisanom makefile-u, promenljiva CFLAGS je uključena u svaku komandu koju pokreće C kompajler i njena uobičajena vrednost je CFLAGS=-g. Svaka C kompilacija se izvršava na sledeći način:

$ cc -c $(CFLAGS) foo.c
što je ekvivalentno sa:

$ cc -c -g foo.c
Možete zameniti ovu vrednost prilikom pokretanja Make-a. Ako CFLAGS postavite na CFLAGS='-g -O', svaka C kompilacija će biti urađena sa sledeći način:

$ cc -c -g -O foo.c
Na ovaj način možete da koristite navodnike u ljusci za zatvaranje razmaka i drugih specijalnih znakova u vrednosti promenljive kada je zamenite.
Ako želite da postavite promenljivu u makefile-u iako je postavljena sa komandnim argumentom, možete da koristite override direktivu na neki od sledećih načina:

override promenljiva = vrednost
override promenljiva := vrednost
Da biste proširili promenljivu definisanu u komandnoj liniji možete koristiti:

override promenljiva += dodatni tekst
Dodeljivanje promenljivih override direktivom ima veći prioritet od svih drugih dodela, osim drugog override-a. Naknadna dodeljivanja ili proširivanje ove promenljive koja nisu označena sa override biće zanemarena.

Direktiva nadjačavanja nije izmišljena za eskalaciju u ratu između makefile-a i komandnih argumenata. Izmišljena je tako da možete da menjate i dodajete vrednosti koje korisnik zadaje komandnim argumentima.

Višelinijske promenljive i define direktiva

Još jedan način za postavljanje vrednosti promenljive je korišćenje define direktive. Ova direktiva ima neobičnu sintaksu koja omogućava da se karakteri novog reda uvrste u vrednost, što je zgodno za definisanje i standardnih sekvenci komandi kao i delova sintakse makefile-a za korišćenje sa eval funkcijom.

Direktiva define je praćena u istom redu imenom promenljive koja se definiše i (opcionim) operatorom dodele. Vrednost koju treba dati promenljivoj pojavljuje se u sledećim redovima. Kraj vrednosti je označen linijom koja sadrži samo reč endef. Poslednji novi red pre endef-a nije uključen u vrednost. Ako želite da vaša vrednost sadrži novi red na kraju, morate uključiti prazan red. Da biste definisali promenljivu koja sadrži znak novog reda, morate koristiti dva prazna reda, a ne jedan.

define newline


endef

Ako se izostavi operator dodele, make pretpostavlja da je = i kreira rekurzivno proširenu promenljivu. Kada koristite operator +=, vrednost se dodaje prethodnoj vrednosti kao i kod bilo koje druge operacije dodavanja, sa jednim razmakom koji razdvaja stare i nove vrednosti.

Ako želite da definicije promenljivih napravljene pomoću define imaju prednost nad definicijama promenljivih iz komandne linije, možete koristiti override direktivu zajedno sa define:

override define two-lines =
foo
$(bar)
endef

Promenljive okruženja

Promenljive u make-u mogu da potiču iz okruženja u kome se make izvodi. Međutim, eksplicitna dodela u makefile-u, ili sa argumentom komande, zamenjuje okruženje. Ako je navedena oznaka -e, tada vrednosti iz okruženja zamenjuju dodele u makefile-u. Ne preporučuje se korišćenje ove opcije!

Postavljanjem promenljive CFLAGS u vašem okruženju, možete prouzrokovati da sve C kompilacije u većini makefile-ova koriste opcije kompajlera koje želite. Ovo je bezbedno za promenljive sa standardnim ili konvencionalnim značenjima jer znate da ih nijedan makefile neće koristiti za druge stvari.

Imajte na umu da ovo nije sasvim pouzdano. Neki makefile-ovi eksplicitno postavljaju CFLAGS i stoga na njih ne utiče vrednost u okruženju.
Razmotrimo sledeći makefile:
$(info YOLO variable = $(YOLO))
Vrednost promenljive YOLO možete postaviti u shell komandi kada pokrećete make:
$ YOLO="Hello world!" make
YOLO variable = Hello world!
make: *** No targets. Stop.

Make prijavljuje ‘’No targets’’ grešku jer naš makefile nema ciljeve.
Ako koristite sintaksu dodele ?=, Make će dodeliti tu vrednost samo ako promenljiva već nema vrednost.
Razmotrimo sledeći makefile:
# defoltni CC u gcc

CC ?= gcc
Sada možemo da zamenimo $(CC) u tom makefile-u:
$ CC=clang make
Drugi uobičajeni obrazac je omogućavanje umetanja dodatnih opcija. U makefile-u bismo dodali promenljivu umesto da joj direktno dodeljujemo.
CFLAGS += -Wall
Ovo dozvoljava popadanje dodatnih opcija iz okruženja:
$ CFLAGS='-Werror=conversion -Werror=double-promotion' make

Promenljive specifične za cilj

Vrednosti promenljivih u Make-u su obično globalne. Iste su bez obzira na to gde se procenjuju, osim ako nisu resetovane. Jedan izuzetak od toga su automatske promenljive, a drugi su promenljive specifične za cilj.

Promenljive specifične za cilj omogućavaju da definišete različite vrednosti za istu promenljivu na osnovu cilja. Kao i kod automatskih promenljivih, ove vrednosti su dostupne samo u kontekstu komandi cilja i u drugim zadacima specifičnim za cilj.

Vrednost promenljive specifične za cilj postavljate na sledeći način:

cilj … : dodela
Višestruke ciljne vrednosti stvaraju vrednost promenljive specifične za cilj za svakog člana ciljne liste pojedinačno. Dodela može biti bilo koji važeći oblik dodele: rekurzivno (=), jednostavno (:=), dodavanje (+=) ili uslovno (?=).

Promenljive specifične za cilj imaju isti prioritet kao i svaka druga makefile promenljiva. Promenljive navedene na komandnoj liniji (i u okruženju ako je na snazi opcija -e) imaće prednost. Navođenje override direktive će omogućiti da se preferira vrednost promenljive specifične za cilj.

Kada definišete promenljivu specifičnu za cilj, ta vrednost promenljive je takođe na snazi za sve preduslove ovog cilja, i sve njihove preduslove, itd. U sledećem primeru vrednost CFLAGS biće postavjena na -g u komandama za prog, ali će takođe CFLAGS imati vrednost -g u komandama koje kreiraju prog.o, foo.o i bar.o.

prog : CFLAGS = -g
prog : prog.o foo.o bar.o

Promenljive specifične za obrazac

Pored vrednosti promenljivih specifičnih za cilj, Make podržava vrednosti promenljivih specifičnih za obrazac. U ovom obliku, promenljiva je definisana za bilo koji cilj koji odgovara navedenom obrascu.

Vrednost promenljive specifične za obrazac možete postaviti na seldeći način, pri čemu je obrazac %-obrazac:

obrazac … : dodela
Kao i kod vrednosti promenljivih specifičnih za cilj, višestruke vrednosti obrazaca stvaraju vrednost promenljive specifične za svaki obrazac pojedinačno. Dodela može biti bilo koji važeći oblik dodele. Bilo koja postavka promenljive komandne linije imaće prednost, osim ako nije navedeno drugačije. U sledećem primeru CFLAGS-u će biti dodeljena vrednost -O za sve ciljeve koji odgovaraju obrascu %.o.

%.o : CFLAGS = -O
Ako cilj odgovara više od jednog obrasca, prvo se tumače odgovarajuće promenljive specifične za obrazac sa dužim osnovama. Ovo dovodi do toga da specifičnije promenljive imaju prednost u odnosu na generičke. U sledećem primeru prva definicija promenljive CFLAGS će se koristiti za ažuriranje lib/bar.o iako se druga takođe primenjuje na ovaj cilj.

%.o: %.c
  $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

lib/%.o: CFLAGS := -fPIC -g
%.o: CFLAGS := -g

all: foo.o lib/bar.o

Promenljive specifične za obrazac koje rezultiraju istom dužinom stabla se razmatraju redosledom kojim su definisane u makefile-u.

Brisanje vrednosti promenljivih i undefine direktiva

Ako želite da obrišete promenljivu, obično je dovoljno postaviti njenu vrednost na praznu. Proširivanje takve promenljive će dati isti rezultat, prazan string, bez obzira da li je postavljena ili ne. Međutim, ako koristite funkcije flavor i origin , postoji razlika između promenljive koja nikada nije postavljena i promenljive sa praznom vrednošću. U takvim situacijama možete koristiti undefine direktivu da promenljiva izgleda kao da nikada nije postavljena.

Razmotrimo sledeći primer:

foo := foo
bar = bar
undefine foo
undefine bar

$(info $(origin foo))
$(info $(flavor bar))

Za obe promenljive biće odštampano undefined .

Ako želite da poništite definiciju promenljive iz komandne linije, možete da koristite override direktivu zajedno sa undefine:

override undefine CFLAGS

Pravila

Makefile se sastoji od više pravila koja imaju sledeći format:

ciljevi : preduslov1 preduslov2 ...
  komanda
  komanda
  ...

Možemo da primetimo da se svako pravilo sastoji od:
  ⇀ Ciljeva (targets)
  ⇀ Liste preduslova (prerequisites list)
  ⇀ Komandi (commands)

Make najpre analizira čitavu datoteku makefile da bi napravio stablo zavisnosti mogućih ciljeva i njihovih preduslova, a potom se iteracijama kreće kroz to stablo da bi napravio potrebne datoteke.
Pravilo govori dve stvari, kada su ciljevi zastareli i kako da ih ažurirate kada je potrebno. Make sprovodi komande na osnovu preduslova za kreiranje ili ažuriranje cilja. Specijalno, pravilo može objasniti kako i kada izvršiti određenu radnju. Kriterijum za zastarelost određen je u smislu preduslova, koji se sastoje od naziva fajlova odvojenih razmacima. Cilj je zastareo ako ne postoji ili ako je stariji od bilo kog od preduslova (poređenjem vremena poslednje izmene). Ideja je da se sadržaj ciljne datoteke izračunava na osnovu informacija u preduslovima, tako da ako se bilo koji od preduslova promeni, sadržaj postojeće ciljne datoteke više neće biti važeći.

Pravilo se smatra uspešnim ako se cilj ažurira nakon što se komande pokrenu, ali nije greška ako se to ne dogodi.

Sintaksa pravila

  ⇀Ciljevi i lista preduslova su odvojeni dvotačkom.
  ⇀Komande počinju tabom.
  ⇀Ciljevi predstaljvaju imena datoteka koje su odvojene blanko karakterom.
  ⇀Obično postoji samo jedan cilj po pravilu, ali ih u nekim slučajevima može biti i više.
  ⇀Mogu se koristiti džokeri.
  ⇀Dugačak red može se podeliti umetanjem obrnute kose crte praćene novim redom. To nije neophodno jer Make ne postavlja ograničenja za dužinu reda u makefileu.

Ako iza obrnute kose crtke sledi jos jedna obrnuta kosa crta onemogućava se njeno specijalno značenje.

Korišćenje džokera u pravilima i funkcija wildcard

Jedno ime datoteke može specificirati mnogo datoteka koristećenjem džoker znakove. Neki od zamenskih znakova u Make-u su zvezdica (*), upitnik (?) i [...]. Na primer, *.c specificira listu svih datoteka u radnom direktorijumu čija se imena završavaju na .c.

Tilda (~) na početku naziva datoteke takođe ima poseban značaj:

  ⇀ Ako je sama ili praćena kosom crtom, predstavlja vaš početni direktorijum. Na primer, ~/bin se proširuje na /home/iou/bin.
  ⇀ Ako iza ~ sledi reč, niz predstavlja početni direktorijum korisnika koji je nazvan tom rečju. Na primer ~john/bin se proširuje na /home/john/bin.

Poseban značaj džoker znaka može se isključiti tako što se ispred njega stavi obrnuta kosa crta. Dakle, foo\*bar bi se odnosio na određenu datoteku čije se ime sastoji od foo, zvezdice i bar.

Džokeri se mogu koristiti u komandama pravila, na primer za brisanje svih objektnih datoteka:

clean:
  rm -f *.o

Džoker znakovi su takođe korisni za preduslove pravila. Sa sledećim pravilom u makefile-u, make print će odštampati sve .c datoteke koje su se promenile od poslednjeg puta kada ste ih odštampali:

print: *.c
  lpr -p $?
  touch print

Ovo pravilo koristi print kao praznu ciljnu datoteku i $? za štampanje samo onih datoteka koje su se promenile.

Proširenje džoker znakova se dešava automatski u pravilima. Ali proširenje džoker znakova se obično ne dešava kada je promenljiva postavljena ili unutar argumenata funkcije. Ako želite da izvršite proširenje džoker znakova na takvim mestima, potrebno je da koristite wildcard funkciju na sledeći način:

$ (wildcard šablon…)
Proširenje džoker znakova se ne dešava kada definišete promenljivu. Dakle, ako napišete:

objects = *.o
onda je vrednost promenljive objects doslovno *.o. Međutim, ako koristite vrednost promenljive objects u cilju ili preduslovu, proširenje džoker znakova će se tamo dogoditi. Takođe, ako je koristite u komandama, shell može izvršiti proširenje džoker znakova kada se komande pokrenu. Međutim, ako koristite funkciju wildcard proširenje će se dogoditi:

objects := $(wildcard *.o)
Jedna upotreba wildcard funkcije je da dobijete listu svih C izvornih datoteka u direktorijumu:

$ (wildcard *.c)
Možete da promenimo listu C izvornih datoteka u listu objektnih datoteka tako što ćete zameniti sufiks .c sa .o u rezultatu:

$ (patsubst %.c,%.o,$(wildcard *.c))
Dakle, makefile za kompajliranje svih C izvornih datoteka u direktorijumu i zatim njihovo povezivanje zajedno može biti napisan na sledeći način:

objects := $(patsubst %.c,%.o,$(wildcard *.c))

foo : $(objects)
  cc -o foo $(objects)

Pravila bez preduslova

Obično pravila sa preduslovima služe za kreiranje ciljne datoteke ako se bilo koji od preduslova promeni. Međutim, pravilo ne mora da ima preduslove. Na primer, pravilo koje sadrži komandu za brisanje sa ciljnim clean nema preduslove.

clean:   # ne zanima nas ako dođe do greške prilikom izvršavanja clean-a   -rm -r ./build
Ako bilo koja komanda vrati izlazni kod koji nije nula, Make će se prekinuti i odštampati poruku o grešci. Možete reći Make-u da ignoriše izlazne kodove koji nisu nula tako što ćete dodati prefiks -.

Implicitna pravila

Često se koriste određeni standardni načini prepravljanja ciljnih datoteka. Implicitna pravila govore Make-u kako da koristi uobičajene tehnike tako da ne morate da ih detaljno navodite kada želite da ih koristite. Ugrađena implicitna pravila koriste implicitne promenljive u svojim komandama tako da, promenom vrednosti promenljivih, možete promeniti način na koji implicitno pravilo funkcioniše.

Na primer, jedan uobičajeni način da se napravi objektna datoteka je iz C izvorne datoteke pomoću C kompajlera, cc. Međutim, postoji implicitno pravilo za C kompilaciju. C kompilacija obično uzima .c datoteku i pravi .o datoteku. Dakle, Make primenjuje implicitno pravilo za C kompilaciju kada vidi ovu kombinaciju ekstenzija.

Implicitna pravila su uvek dostupna osim ako ih makefile eksplicitno ne predefiniše ili poništi. Da biste videli punu listu podrazumevanih pravila i promenljivih dostupnih u vašoj verziji Make-a, pokrenite sledeću komandu u direktorijumu bez makefile-a:

$ make -p
Neka implicitna pravila data su u tabeli:

Kompilacija C programa n.o se automatski pravi od n.c komandom $(CC) $(CPPFLAGS) $(CFLAGS) -c
Kompilacija C++ programa n.o se automatski pravi od n.cc, n.cpp ili n.C komandom $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c
Kompilacija Pascal programa n.o se automatski pravi od n.p komandom $(PC) $(PFLAGS) -c

Opcija -r ili --no-builtin-rules poništava sva unapred definisana pravila.

Ciljevi

Ciljevi su leva strana u sintaksi pravila. Skoro uvek imenuju datoteke koje se generišu od strane Make-a. To je zato što Make koristi vreme poslednje izmene da prati da li je cilj noviji ili stariji od njegovih preduslova i da li ga treba ponovo izgraditi.
Kada pozivate Make, možete odrediti koje ciljeve želite da izgradite kao ciljeve tako što ćete ih navesti kao pozicioni argument:

# postavi 'test.txt' i 'all.zip' za ciljeve make test.txt all.zip
Ako ne navedete cilj u komandi, Make koristi prvi cilj naveden u makefile-u, i on se naziva podrazumevani cilj.

Ugrađena imena ciljeva

Određena imena imaju posebno značenje ako se pojavljuju kao ciljevi. Neka od njih data su u sledećoj tabeli:

ime cilja značenje
.PHONY Preduslovi se smatraju lažnim ciljevima. Make će se pokrenuti bezuslovno, bez obzira da li datoteka sa tim imenom postoji ili koje je vreme njene poslednje izmene.
.DEFAULT Komande navedene za .DEFAULT se koriste za bilo koji cilj za koji nisu pronađena pravila.
.PRECIOUS Ciljevi od kojih zavisi .PRECIOUS se ne brišu ako je make prekinut tokom izvršavanja komandi.
.INTERMEDIATE Ciljevi od kojih zavisi .INTERMEDIATE tretiraju se kao međufajlovi. .INTERMEDIATE bez preduslova nema efekta
.SECONDARY Ciljevi od kojih zavisi .SECONDARY se tretiraju kao međufajlovi, osim što se nikada ne brišu automatski. .SECONDARY bez preduslova dovodi do toga da se svi ciljevi tretiraju kao sekundarni, tj. nijedan cilj se ne uklanja jer se smatra međufajlom.
.DELETE_ON_ERROR Ako se .DELETE_ON_ERROR spominje kao cilj bilo gde u makefile-u, onda će Make izbrisati cilj pravila ako se promenilo i njegova komanda izlazi sa izlaznim statusom koji nije nula.
.IGNORE Ako navedete preduslove za .IGNORE, make će ignorisati greške u izvršavanju komandi za te određene datoteke. Komande za .IGNORE (ako postoje) se ignorišu. Ako se pominje kao cilj bez preduslova, .IGNORE kaže da ignoriše greške u izvršavanju komandi za sve datoteke.

Lažni ciljevi

Ciljevi ne moraju da imenuju datoteke. U dva slučaja možete koristit lažne ciljeve:

  ⇀da biste izbegli konflikt sa datotekom istog imena
  ⇀da biste poboljšali performanse

Lažni ciljevi se zapravo koriste kada je potrebno izvršiti određenu radnju poput clean, all, test, itd. U tim slučajevima ne želite da Make proverava da li postoji datoteka pod tim imenom.

Ako napišete pravilo čija komanda neće kreirati ciljnu datoteku, komanda će biti izvršena svaki put kada se cilj ponovo gradi.
clean:
  rm *.o temp

Pošto komanda rm ne kreira datoteku pod nazivom clean, verovatno takva datoteka nikada neće postojati. Prema tome, komanda rm će biti izvršena svaki put kada pokrenete make clean. Cilj clean neće funkcionisati ispravno ako je datoteka pod nazivom clean ikada kreirana u ovom direktorijumu. Pošto nema preduslova, clean bi se uvek smatrao ažurnim i njegova komanda se ne bi izvršavala. Da biste izbegli ovaj problem, možete eksplicitno da proglasite cilj lažnim tako što ćete ga učiniti preduslovom za specijalni cilj .PHONY na sledeći način:

.PHONY: clean
clean:
  rm *.o temp

Sada će make clean pokrenuti komandu bez obzira da li postoji datoteka pod nazivom clean.

Lažni ciljevi mogu imati preduslove. Kada jedan direktorijum sadrži više programa, najpogodnije je opisati sve programe u jednom makefile-u. Pošto će cilj koji je podrazumevano prepravljen biti prvi u makefile-u, uobičajeno je da se ovo učini lažnim ciljem pod nazivom all i da mu se, kao preduslovi, daju svi pojedinačni programi.

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
  cc -o prog1 prog1.o utils.o

prog2 : prog2.o
  cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
  cc -o prog3 prog3.o sort.o utils.o

Sada naredbom make možete pokrenuti sva tri programa, ili navesti kao argumente one koje želite.

Lažnost se ne nasleđuje! Preduslovi lažnog cilja nisu sami po sebi lažni, osim ako nije eksplicitno proglašeno da jesu.

Prazne ciljne datoteke

Prazan cilj je varijanta lažnog cilja. Koristi se za čuvanje komandi za radnju koju s vremena na vreme eksplicitno zahtevate. Za razliku od lažnog cilja, ova ciljna datoteka može zaista postojati, ali sadržaj datoteke nije bitan i obično je prazan. Njena svrha je da zabeleži, sa vremenom poslednje izmene, kada su komande pravila poslednji put izvršene.

Prazna ciljna datoteka treba da ima neke preduslove. Kada zatražite da se prepravi prazan cilj, komande će se izvršavati ako je neki preduslov noviji od cilja, tj. ako se preduslov promenio od poslednjeg puta kada ste prepravljali cilj.

U sledećem primeru, make print će izvršiti komandu lpr ako se bilo koja izvorna datoteka promenila od poslednjeg make print-a. Automatska promenljiva $? se koristi za štampanje samo onih datoteka koje su se promenile.

print: foo.c bar.c
  lpr -p $?
  touch print

Automatsko generisanje ciljeva

Mnoga pravila u makefile-u govore samo da neki objektni fajl zavisi od neke datoteke zaglavlja. Na primer, ako main.c koristi defs.h preko #include, napisali biste:

main.o: defs.h
Ovo pravilo vam je potrebno kako bi Make znao da mora ponovo napraviti main.o kad god se defs.h promeni. Za veliki program biste morali da upišete desetine takvih pravila u svoj makefile. Takođe, morali biste da ažurirate makefile svaki put kada dodate ili uklonite #include.

Da biste izbegli ovo, većina modernih C kompajlera može da napiše ova pravila za vas, posmatrajući #include redove u izvornim datotekama, pomoću opcije -M za kompajler.

$ cc -M main.c
Ova komanda generiše izlaz:

main.o : main.c defs.h
Praksa koju preporučujemo za automatsko generisanje preduslova je da imate jedan makefile koji odgovara svakoj izvornoj datoteci. Za svaki izvorni fajl name.c postoji makefile name.d koji navodi od kojih datoteka zavisi ime datoteke object.o. Na taj način samo izvorne datoteke koje su promenjene treba ponovo da se skeniraju da bi se proizveli novi preduslovi.

Za GCC kompajler, umesto opcije -M potrebno je da koristite opciju -MM. Ovo izostavlja preduslove za datoteke zaglavlja sistema.

Višestruki ciljevi

Kada eksplicitno pravilo ima više ciljeva, oni se mogu tretirati na jedan od dva moguća načina: kao nezavisni ciljevi ili kao grupisani ciljevi. Način na koji se oni tretiraju određuje se separatorom koji se pojavljuje posle liste ciljeva.

Pravila sa nezavisnim ciljevima

Pravila koja koriste standardni separator ciljeva, :, definišu nezavisne ciljeve. Ovo je ekvivalentno pisanju istog pravila jednom za svaki cilj, sa dupliranim preduslovima i komandama.

Pravila sa nezavisnim ciljevima su korisnna kada:

želite samo preduslove
slične komande odgovaraju svim ciljevima

Sledeći primer daje dodatni preduslov za svaku od tri pomenute objektne datoteke:

kbd.o command.o files.o: command.h
To je ekvivalentno pisanju:

kbd.o: command.h
command.o: command.h
files.o: command.h

U narednom primeru automatska promenljiva $@ se koristiti za zamenu određenog cilja koji treba da se koristi u komandama.

bigoutput littleoutput : text.g
  generate text.g -$(subst output,,$@) > $@

Ovo je ekvivalentno sa:

bigoutput : text.g
  generate text.g -big > bigoutput
littleoutput : text.g
  generate text.g -little > littleoutput

Pravila sa grupisanim ciljevima

Ako umesto nezavisnih ciljeva imate komande koje generišu više datoteka iz jednog poziva, možete izraziti taj odnos tako što ćete naglasiti da pravilo koristi grupisane ciljeve. Grupisano ciljno pravilo koristi separator &:, gde & implicira „sve“.

Kada make izgradi bilo koji od grupisanih ciljeva, on razume da su svi ostali ciljevi u grupi takođe kreirani kao rezultat pozivanja komandi. Štaviše, ako su samo neki od grupisanih ciljeva zastareli ili nedostaju Make će shvatiti da će pokretanje komandi ažurirati sve ciljeve.

Sledeće pravilo definiše grupisani cilj:

foo bar biz &: baz boz
  echo $^ > foo
  echo $^ > bar
  echo $^ > biz

Tokom izvršavanja komandi grupisanog cilja, automatska promenljiva $@ se postavlja na ime određenog cilja u grupi koji je pokrenuo pravilo.

Za razliku od nezavisnih ciljeva, pravilo grupisanog cilja mora da sadrži komande. Međutim, ciljevi koji su članovi grupisanog cilja mogu se pojaviti i u nezavisnim definicijama pravila cilja koje nemaju komande.

Preduslovi

Preduslov je datoteka koja se koristi kao ulaz za kreiranje cilja. Obično zavili od nekoliko datoteka. Ako je bilo koji preduslov noviji (poređenjem vremena poslednje izmene) od cilja, Make će pokrenuti ciljno pravilo.

Postoje dva različita tipa preduslova:
  ⇀ normalni preduslovi
  ⇀ preduslovi sa redosledom

Normalni preduslovi

Nomralni preduslov nameće redosled po kome će se komande pozivati kao i odnos zavisnosti. Komande za sve preduslove cilja će biti završene pre nego što se pokrenu komande za cilj. Ako je bilo koji preduslov noviji od cilja, onda se cilj smatra zastarelim i mora se ponovo izgraditi.

Preduslovi sa redosledom

Povremeno želite da nametnete određeni redosled pravila koja će se pozivati bez prisiljavanja da se cilj ažurira ako se jedno od tih pravila izvrši. U tom slučaju želite da definišete preduslove samo za porudžbinu. Preduslovi sa redosledom mogu se navesti postavljanjem uspravne crte (|) na listu preduslova. Svi preduslovi levo od simbola cevi su normalni, a svi preduslovi sa desne strane su sa redosledom.

ciljevi : normalni-preduslovi | preduslovi-sa-redosledom
Normalni odeljak preduslova može biti prazan. Takođe, i dalje možete deklarisati više redova preduslova za isti cilj. Oni se dodaju na odgovarajući način (normalni preduslovi se dodaju listi normalnih preduslova, preduslovi sa redosledom se dodaju listi preduslova sa redosledom).

Ako deklarišete istu datoteku kao normalan preduslov i preduslov sa redosledom, normalni preduslov ima prednost.
Tipičan primer je kreiranje direktorijuma za objektne datoteke. Vaši ciljevi treba da budu smešteni u poseban direktorijum, a taj direktorijum možda neće postojati pre nego što se pokrene make. U ovoj situaciji, želite da se direktorijum kreira pre nego što se u njega stave ciljevi, ali pošto se vremenske oznake na direktorijumima menjaju svaki put kada se datoteka doda, ukloni ili preimenuje, mi svakako ne želimo da ponovo izgradimo sve ciljeve kad god se promene vremenske oznake direktorijuma. Jedan od načina da se ovo uradi je pomoću preduslova sa redosledom tako što ćete učinite da direktorijum bude preduslov sa redosledom na svim ciljevima.

OBJDIR := objdir
OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

$(OBJDIR)/%.o : %.c
  $(COMPILE.c) $(OUTPUT_OPTION) $<

all: $(OBJS)

$(OBJS): | $(OBJDIR)

$(OBJDIR):
  mkdir $(OBJDIR)

Sada će se pravilo za kreiranje direktorijuma objdir pokrenuti, ako je potrebno, pre nego što se napravi .o, ali neće biti napravljen .o jer je vremenska oznaka direktorijuma objdir promenjena.

Pretrazivanje preduslova u direktorijumima

Za velike sisteme, često je poželjno staviti izvore u poseban direktorijum od binarnih datoteka. Funkcije pretrage direktorijuma olakšavaju ovo tako što automatski pretražuje nekoliko direktorijuma da bi se pronašao preduslov. Kada ponovo distribuirate datoteke između direktorijuma, ne morate da menjate pojedinačna pravila, već samo putanje za pretragu.

Opšta pretraga i VPATH

Vrednost promenljive VPATH navodi listu direktorijuma koje bi Make trebalo da pretraži. Najčešće se očekuje da direktorijumi sadrže datoteke preduslova koje se ne nalaze u trenutnom direktorijumu. Međutim, Make koristi VPATH kao listu za pretragu i za preduslove i za ciljeve pravila.

Ako datoteka koja je navedena kao cilj ili preduslov ne postoji u trenutnom direktorijumu, Make traži datoteku sa tim imenom u direktorijumima navedenim u VPATH-u. Ako se datoteka nađe u jednom od njih, ta datoteka može postati preduslov, a pravila tada mogu specificirati imena datoteka na listi preduslova kao da sve postoje u trenutnom direktorijumu.

U promenljivoj VPATH, imena direktorijuma su odvojena dvotačkama ili prazninama. Redosled u kome su direktorijumi navedeni je redosled koji Make prati prilikom pretrage.

U sledećem primeru VPATH specificira putanju koja sadrži dva direktorijuma, src i ../headers, u kojima se vrše pretrage, tim redosledom.

VPATH = src:../headers
Pretpostavimo da datoteka foo.c ne postoji u trenutnom direktorijumu, ali se nalazi u direktorijumu src. Sledeće pravilo

foo.o : foo.c
tumači se kao:

foo.o : src/foo.c

Selektivna pretraga i vpath direktiva

Slična promenljivoj VPATH, ali selektivnija, je vpath direktiva koja omogućava da odredite putanju za pretragu za određenu klasu imena datoteka, one koje odgovaraju određenom šablonu. Tako možete obezbediti određene direktorijume za pretragu za jednu klasu imena datoteka i druge direktorijume (ili nijedan) za drugu klasu imena datoteka.

Postoje tri oblika vpath direktive:

vpath obrazac direktorijumi Određuje direktorijume putanje za pretragu za imena datoteka koja odgovaraju obrascu. Putanja za pretragu, direktorijumi, je lista direktorijuma koje treba pretraživati, odvojenih tačkama ili prazninama, baš kao i putanja za pretragu koja se koristi u promenljivoj VPATH.
vpath obrazac Briše putanju za pretragu koja je povezana sa obrascem.
vpath Briše sve putanje za pretragu koje su prethodno navedene sa vpath direktivama.

Obrazac je niz koji sadrži znak %. String mora da odgovara imenu datoteke preduslova koji se traži, znak % koji odgovara bilo kojoj sekvenci od nula ili više znakova. Na primer, %.h odgovara datotekama koje se završavaju na .h.

Ako nema %, obrazac mora tačno da odgovara preduslovu, što često nije korisno.
Sledeći primer kaže Make-u da potraži bilo koji preduslov čije se ime završava na .h u direktorijumu ../headers ako datoteka nije pronađena u trenutnom direktorijumu.

vpath %.h ../headers
Ako se nekoliko obrazaca podudara sa imenom datoteke preduslova, izvršite obradu svake odgovarajuće vpath direktive jednu po jednu, pretražujući sve direktorijume pomenute u svakoj direktivi. Make obrađuje više vpath direktiva po redosledu kojim se pojavljuju u makefile-u. Više direktiva sa istim obrascem su nezavisne jedna od druge.

Sledeći primer će tražiti datoteku koja se završava na .c u foo, zatim blish, pa bar:

vpath %.c foo
vpath %.c blish
vpath %.c bar

Komande

Sintaksa komandi

Makefile imaju neobično svojstvo da postoje dve različite sintakse u jednoj datoteci. Većina makefile-ova koristi Make sintaksu. Međutim, komande su osmišljene tako da mogu da budu interpretiranw od strane ljuske i zato se pišu korišćenjem shell sintakse.

  ⇀ Pravilo može da se sastoji od jedne ili više komandi koje se izvršavaju.
  ⇀ Prva komanda može da se javi u liniji ispod preduslova ili u istoj liniji sa preduslovima ali odvojena ;.
  ⇀ Svaka komanda u pravilu mora da počinje tabulatorom.
Znak kojim počinje komanda možete promeniti podešavanje promenljive .RECIPEPREFIX na alternativni znak. Ako je promenljiva prazna (kao što je podrazumevano), taj znak je standardni tabulator.

.RECIPEPREFIX = >
all:
> @echo Hello, world

Vrednost .RECIPEPREFIX se može promeniti više puta. Kada se jednom postavi na određenu vrednost, ostaje na snazi za sva pravila dok se ne izmeni.
  ⇀ Prazni redovi i komentari mogu se pojaviti među komandama i ignorišu se.

Neke posledice ovih pravila uključuju:

  ⇀ Prazan red koji počinje tabulatorom nije prazan, to je prazna komanda.
  ⇀ Komentar u receptu nije komentar; biće prosleđen ljusci kakav jeste. Da li će ljuska to tretirati kao komentar ili ne zavisi od vaše ljuske.
  ⇀ Definicija promenljive u koja je uvučena tabulatorom kao prvim znakom na liniji, smatraće se delom komandi, a ne definicijom Make promenljive, i prosleđivaće se ljusci.
  ⇀ Uslovni izraz (ifdef, ifek, itd) koji je uvučen tabulatorom kao prvi znak na liniji, smatraće se delom komandi i prosleđivaće se ljusci.

Izvršavanje

Kada dođe vreme za ažuriranje cilja, komande se izvršavaju pozivanjem nove pod-ljuske za svaki red.

Ponekad ćete želeti da se svi redovi u komandama prosleđuju u jedno pozivanje ljuske. Ako se specijalni cilj .ONESHELL pojavi bilo gde u makefile-u, onda će sve komande za svaki cilj biti prosleđene za jedno pozivanje ljuske. Novi redovi između redova recepta će biti sačuvani. Postoje dve situacije u kojima je ovo korisno:
  ⇀ kada želite da poboljšate performanse u makefile-ovima gde u pravilima ima mnogo komandnih linija, izbegavanjem dodatnih procesa
  ⇀ kada želite da novi redovi budu uključeni u vašu komandu

Greške

Nakon što se vrati svaki poziv ljuske, možete pogledati njegov izlazni status. Ako je ljuska uspešno završena, sa izlaznim statusom nula, sledeća komanda se izvršava u novoj ljusci. Nakon što je poslednja komanda završena, pravilo je završeno. Ako postoji greška, sa izlaznim statusom različitim od nule, Make odustaje od trenutnog pravila, a u nekim slučajevima i od svih pravila.

Ponekad neuspeh određene linije ne ukazuje na problem. Na primer, možete koristiti komandu mkdir da biste bili sigurni da direktorijum postoji. Ako direktorijum već postoji, mkdir će prijaviti grešku, ali verovatno želite da se make svakako nastavi.

Da biste ignorisali greške možete koristiti prefiks -.
U sledećem primeru make se nastavlja čak i ako rm ne može da ukloni datoteku:

clean:
  -rm -f *.o

Kada pokrenete make sa opcijama -i ili --ignore-errors, greške se zanemaruju u svim komandama svih pravila. Pravilo u makefile-u za specijalni cilj .IGNORE ima isti efekat, ako nema preduslova. Kada se greške ignorišu, zbog oznake - ili -i, make tretira vraćanje greške kao uspeh, štampa poruku koja vam govori statusni kod sa kojim je ljuska izašla, i kaže da je greška zanemarena.

Funkcije

Sintaksa za pozivanje funkcija

Poziv funkcije liči na referencu promenljive. Može se pojaviti svuda gde se može pojaviti referenca promenljive i proširuje se korišćenjem istih pravila kao i reference promenljivih. Poziv funkcije izgleda ovako:

$ (funkcija argumenti)
$ {funkcija argumenti}
Ovde funkcija predstavlja ime jedne od funkcija koje su deo Make-a. Takođe, možete kreirati sopstvene funkcije korišćenjem call ugrađene funkcije.

Argumenti su odvojeni od naziva funkcije sa jednim ili više razmaka ili tabulatora, a ako postoji više od jednog argumenata oni se odvajaju zarezima. Takvi razmaci i zarezi nisu deo vrednosti argumenta. Graničnici koje koristite da okružite poziv funkcije, bilo da su obične ili vitičaste zagrade, mogu se pojaviti u argumentu samo u parovima koji se podudaraju. Druga vrsta graničnika se može pojaviti pojedinačno. Ako sami argumenti sadrže pozive drugih funkcija ili reference promenljivih, najpametnije je koristiti istu vrstu graničnika za sve reference. Bolje je napisati

$(subst a,b,$(k))
a ne

$(subst a,b,${k})
To je zato što je jasnije i zato što se samo jedan tip graničnika podudara da bi se pronašao kraj reference.

Zarezi i zagrade (bilo usklađene ili neusklađene) ne mogu se pojaviti u tekstu argumenta. Ovi znakovi se mogu staviti u vrednost argumenta zamenom promenljive. Prvo definišite promenljive zarez i razmak čije su vrednosti izolovani zarez i razmak, a zatim zamenite ove promenljive tamo gde se takvi znakovi traže:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now ‘a,b,c’.

Funkcija subst zamenjuje svaki razmak zarezom, kroz vrednost foo, i zamenjuje rezultat.

Funkcije za zamenu i analizu stringova

$(subst from,to,tekst)

Svako pojavljivanje from u tekstu se zamenjuje sa to.
Sledeći primer proizvodi vrednost ’fEEt on the strEEt’.

$(subst ee,EE,feet on the street)

$(patsubst šablon,zamena,tekst)

Pronalazi reči razdvojene razmacima u tekstu koje odgovaraju šablonu i zamenjuje ih zamenom. Šablon može da sadrži % koji deluje kao džoker znak i odgovara bilo kom broju bilo kog karaktera u reči. Ako zamena takođe sadrži %, on se zamenjuje tekstom koji se podudara sa % u šablonu. Samo prvi % u šablonu i zameni se tretira na ovaj način, a svaki sledeći biva nepromenjen. Razmaci između reči se predstavljaju kao jedan razmak, a vodeći i završni razmaci se odbacuju.
Sledeći primer proizvodi vrednost ’k.c.o bar.o’:

$(patsubst %.c,%.o,x.c.c bar.c)

$(string string)

Uklanja vodeći i završni razmak iz stringa i svaki interni niz od jednog ili više znakova razmaka zamenjuje jednim razmakom. Može biti veoma korisna kada se koristi u kombinaciji sa uslovima.
Sledeći primer rezultira ’a b c’:

$(strip a b c)

$(findstring find,tekst)

Pretražuje pojavljivanje find-a u tekstu. Ako ga pronađe, vrednost je find, a u suprotnom vrednost je prazna. Ovu funkciju možete koristiti u uslovu da biste testirali prisustvo određenog podniza u datom nizu.
Prvi primer će proizvesti vrednost ’a’, a drugi ’’ (prazan string), respektivno:

$(findstring a,a b c)
$(findstring a,b c)

$(filter šablon...,tekst)

Vraća sve reči u tekstu razdvojene razmacima koje se podudaraju sa bilo kojom od šablonskih reči, uklanjajući sve reči koje se podudaraju. Šabloni su napisani pomoću %, kao i kod patsubst funkcije.

$(filter-out obrazac…,tekst)

Ova fukcija je suprotna funkciji filter. Vraća sve reči u tekstu razdvojene razmakom koje se ne podudaraju ni sa jednom od šablonskih reči.

$(sort lista)

Sortira reči liste leksikografski, uklanjajući duple reči. Izlaz je lista reči razdvojenih jednim razmakom. Ako redosled nije bitan, može se koristiti za uklanjanje duplikata reči.
Sledeći primer vraća ’bar foo lose’:

$(sort foo bar lose)

$(word n,tekst)

Vraća n-tu reč teksta. Legitimne vrednosti n počinju od 1. Ako je n veće od broja reči u tekstu, vrednost je prazna.
Sledeći primer vraća ’bar’:

$(word 2, foo bar baz)

$(wordlist s,e,tekst)

Vraća listu reči u tekstu koje počinju rečju s i završavaju se rečju e (uključujući i nju). Legitimne vrednosti s počinju od 1, dok e može da počinje od 0. Ako je s veći od broja reči u tekstu, vrednost je prazna. Ako je e veće od broja reči u tekstu, vraćaju se reči do kraja teksta. Ako je s veće od e, ništa se ne vraća.
Sledeći primer vraća ’bar baz’:

$(wordlist 2, 3, foo bar baz)

$(words tekst)

Vraća broj reči u tekstu.

$(firstwords imena...)

Imena argumenata se smatraju nizom imena, odvojenih razmakom. Vrednost je prvo ime u nizu. Ostala imena se ignorišu.
Sledeći primer vraća ’foo’:

$(firstword foo bar)

$(lastwords imena…)

Imena argumenata se smatraju nizom imena, odvojenih razmakom. Vrednost je poslednje ime u nizu.
Sledeći primer vraća ’bar’:

$(lastword foo bar)

Funkcije za imena datoteka

Sledeće ugrađene funkcije proširenja se odnose na rastavljanje imena datoteka ili listi imena datoteka. Svaka od njih vrši određenu transformaciju imena datoteke. Argument funkcije se smatra nizom imena datoteka, odvojenih razmakom, pri čemu se vodeći i završni razmaci zanemaruju. Svako ime datoteke u nizu se transformiše na isti način i rezultati se spajaju sa pojedinačnim razmacima između njih.

$(dir imena...)

Izvlači direktorijum-deo svakog imena datoteke u imenima. Direktorijum-deo imena datoteke je sve do poslednje kose crte (uključujući i nju). Ako ime datoteke ne sadrži kosu crtu, direktorijum-deo je string ’./’.
Sledeći primer proizvodi rezultat ’src/ ./’:

$(dir src/foo.c hacks)

$(notdir imena...)

Izvlači sve osim direktorijum-dela iz imena svake datoteke u imenima. Ako naziv datoteke ne sadrži kosu crtu, ostaje nepromenjen. U suprotnom, iz njega se uklanja sve do poslednje kose crte (uključujući i nju). Ime datoteke koje se završava kosom crtom postaje prazan string. To može da dovede do toga da rezultat nema uvek isti broj imena datoteka razdvojenih razmacima kao što je imao argument.
Sledeći primer proizvodi rezultat ’foo.c hacks’.

$(notdir src/foo.c hacks)

$(suffixs imena...)

Izvlači sufiks svakog imena datoteke u imenima. Ako ime datoteke sadrži tačku, sufiks je sve od poslednje tačke do kraja. Inače, sufiks je prazan niz. Ovo često znači da će rezultat biti prazan kada imena nisu, a ako imena sadrže više imena datoteka, rezultat može sadržati manje imena datoteka.
Sledeći primer proizvodi rezultat ’.c .c’:

$(suffix src/foo.c src-1.0/bar.c hacks)

$(basename imena...)

Izdvaja sve osim sufiksa imena svake datoteke u imenima. Ako ime datoteke sadrži tačku, osnovno ime je sve od početka do poslednje tačke (ne uključujući nju). Tačke u delu direktorijuma se zanemaruju. Ako nema tačke, osnovno ime je celo ime datoteke.
Sledeći primer proizvodi rezultat ’src/foo src-1.0/bar hacks’:

$(basename src/foo.c src-1.0/bar hacks)

$(addsufiks sufiks, imena…)

Imena argumenata se smatraju nizom imena, odvojenim razmakom. Vrednost sufiksa se dodaje na kraj svakog pojedinačnog imena.
Sledeći primer proizvodi rezultat ’foo.c bar.c’:

$(addsuffix .c,foo bar)

$(addprefix prefiks,imena…)

Imena argumenata se smatraju nizom imena, odvojenim razmakom. Vrednost prefiksa se dodaje ispred svakog pojedinačnog imena.
Sledeći primer proizvodi rezultat ’src/foo src/bar’:

$(addprefix src/,foo bar)

$(join lista1, lista2)

Povezuje dva argumenta reč po reč. Dve prve reči (po jedna iz svakog argumenta) spojene čine prvu reč rezultata, dve druge reči čine drugu reč rezultata, itd. Dakle, n-ta reč rezultata dolazi od n-te reči svakog argumenta. Ako jedan argument ima više reči od drugog, dodatne reči se kopiraju nepromenjene u rezultat. Ova funkcija može spojiti rezultate funkcija dir i notdir, da bi proizvela originalnu listu datoteka koja je data tim dvema funkcijama.
Sledeći primer proizvodi ’a.c b.o’:

$(join a b,.c .o)

$(wildcard šablon)

Argument šablon je šablon naziva datoteke, koji obično sadrži džoker znakove. Rezultat je lista imena postojećih datoteka razdvojenih razmacima koja odgovaraju šablonu.

$(realpath imena…)

Za svako ime datoteke u imenima vraća kanonsko apsolutno ime. Kanonsko ime ne sadrži nikakve . ili .. komponente, niti bilo koji ponovljeni separator putanje (/) ili simboličke veze. U slučaju neuspeha vraća se prazan string.

$(abspath imena…)

Za svako ime datoteke u imenima vraća apsolutno ime koje ne sadrži nikakve . ili .. komponente, niti bilo koji ponovljeni separator putanje (/). Za razliku od funkcije realpath, abspath ne rešava simbolične veze i ne zahteva da se imena datoteka odnose na postojeću datoteku ili direktorijum.

Funkcije za uslove

Postoje tri funkcije koje obezbeđuju uslovno proširenje. Ključni aspekt ovih funkcija je da nisu svi argumenti u početku prošireni. Biće prošireni samo oni argumenti koje treba proširiti.

$(if uslov, onda-deo [, inače-deo])

Funkcija if pruža podršku za uslovno proširenje u funkcionalnom kontekstu.
Prvom argumentu, uslov, najpre se uklanjaju razmaci sa početka i kraja, a zatim se proširuje. Ako se proširi na bilo koji niz koji nije prazan, onda se smatra da je uslov tačan. Ako se proširuje na prazan niz, uslov se smatra netačnim. Ako je uslov tačan, onda se procenjuje drugi argument, onda-deo, i ovo se koristi kao rezultat procene cele if funkcije. Ako je uslov netačan, onda se procenjuje treći argument, inače-deo i ovo je rezultat funkcije if. Ako ne postoji treći argument, funkcija if ne vredi ništa (prazan string).

Samo će jedan od onda-dela i inače-dela biti procenjen, nikada oba.

$(or uslov1 [, uslov2 [, uslov3...]])

Funkcija or obezbeđuje operaciju „kratkog spoja“ OR. Svaki argument je proširen, po redu. Ako se argument proširi na neprazan string, obrada se zaustavlja i rezultat proširenja je taj string. Ako su, nakon što su svi argumenti prošireni, svi netačni (prazni), onda je rezultat proširenja prazan string.

$(and uslov1 [, uslov2 [, uslov3...]])

Funkcija and obezbeđuje operaciju „kratkog spoja“ AND. Svaki argument je proširen, po redu. Ako se argument proširi na prazan string, obrada se zaustavlja i rezultat proširenja je prazan string. Ako se svi argumenti prošire na neprazan string onda je rezultat proširenja proširenje poslednjeg argumenta.

Funkcija foreach

Funkcija foreach omogućava da se jedan deo teksta koristi više puta, svaki put sa različitom zamenom. Podseća na for i foreach petlju.

$(foreach var,lista,tekst)

Prva dva argumenta, var i lista, se proširuju pre nego što se bilo šta drugo uradi. Poslednji argument, tekst, ne proširuje se u isto vreme. Zatim se za svaku reč proširene vrednosti liste, promenljiva imenovana proširenom vrednošću var postavlja na tu reč, a tekst se proširuje. Zapravo, tekst sadrži reference na tu promenljivu, tako da će njegovo proširenje svaki put biti drugačije. Rezultat je da se tekst proširuje onoliko puta koliko ima reči razdvojenih razmacima u listi.

Ovaj jednostavan primer postavlja promenljivu files na listu svih datoteka u direktorijumima na listi dirs:

dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))

Ovde je tekst ’$(vildcard $(dir)/*)’. Prvo ponavljanje pronalazi vrednost ’a’ za dir, tako da daje isti rezultat kao ’$(vildcard a/*)’. Drugo ponavljanje daje rezultat ’$(vildcard b/*)’, i treće ’$(vildcard c/*)’.

Sledeći primer ima isti rezultat:

files := $(wildcard a/* b/* c/* d/*)
Kada je tekst komplikovan, možete poboljšati čitljivost tako što ćete mu dati ime, uz dodatnu promenljivu:

find_files = $(wildcard $(dir)/*)
dirs := a b c d
files := $(foreach dir,$(dirs),$(find_files))

Funkcija datoteke

Funkcija file omogućava makefile-u da piše ili čita iz datoteke. Podržana su dva načina pisanja:

  ⇀ prepisivanje - tekst se upisuje na početak datoteke i sav postojeći sadržaj se gubi
  ⇀ dodavanje - tekst se upisuje na kraj datoteke, čuvajući postojeći sadržaj.
U oba slučaja datoteka se kreira ako ne postoji, a funkcija file se proširuje na prazan string. Ako se datoteka ne može otvoriti za pisanje ili ako operacija pisanja ne uspe dolazi do fatalne greške.

Prilikom čitanja iz datoteke, funkcija file se proširuje na doslovni sadržaj datoteke, osim što će konačni novi red, ako postoji, biti uklonjen. Pokušaj čitanja iz nepostojeće datoteke proširuje se na prazan string.

$(file op ime[,tekst])

Kada se proceni funkcija file, svi njeni argumenti se prvo prošire, pa se zatim otvara datoteka označena imenom u režimu opisanom u op.

Operator op može biti:

  ⇀ > označava da će datoteka biti prepisana novim sadržajem
  ⇀ >> označava da će trenutni sadržaj datoteke biti dodat
  ⇀ < označava da će sadržaj datoteke biti pročitan

Funkcija poziva

Funkcija call je jedinstvena po tome što se može koristiti za kreiranje novih parametrizovanih funkcija. Možete napisati složeni izraz kao vrednost promenljive, a zatim koristiti poziv da ga proširite različitim vrednostima.

$(call promenljiva,param,param,…)

Budite oprezni kada dodajete razmak argumentima za pozivanje. Kao i kod drugih funkcija, svaki razmak sadržan u drugom i narednim argumentima se čuva što može izazvati čudne efekte. Generalno je najsigurnije ukloniti sve suvišne razmake kada dajete parametre za pozivanje.
Kada Make proširi ovu funkciju, dodeljuje svaki param privremenim promenljivim $(1), $(2), itd. Promenljiva $(0) će sadržati promenljivu. Ne postoji maksimalan broj parametara. Ne postoji ni minimalan, ali nema smisla koristiti poziv bez parametara.

Zatim se promenljiva proširuje kao Make promenljiva u kontekstu ovih privremenih dodela. Dakle, svaka referenca na $(1) u vrednosti promenljive će se razrešiti na prvi param u pozivanju poziva.

Promenljiva je ime promenljive, a ne referenca na tu promenljivu. Zbog toga ne treba da koristite $ ili zagrade kada ga pišete.
Ako je promenljiva ime ugrađene funkcije, ugrađena funkcija se uvek poziva.

Ovaj makro jednostavno preokreće svoje argumente, nakon čega će foo sadržati ’b a’:
reverse = $(2) $(1)
foo = $(call reverse,a,b)

Funkcija vrednosti

Funkcija value omogućava korišćenje vrednosti promenljive bez njenog proširenja. Ovo ne poništava proširenja koja su se već desila. Na primer, ako kreirate jednostavno proširenu promenljivu, njena vrednost se proširuje tokom definicije i u tom slučaju funkcija vrednosti će vratiti isti rezultat kao direktno korišćenje promenljive.

$ (value promenljiva)

Promenljiva je ime promenljive, a ne referenca na tu promenljivu. Zbog toga ne treba da koristite $ ili zagrade kada ga pišete.
Rezultat sledeće funkcije je string koji sadrži vrednost promenljive, bez ikakvog proširenja.

FOO = $PATH

all:
@echo $(FOO)
@echo $(value FOO)

U ovom makefile-u, prva izlazna linija bi bila ATH, pošto bi „$P“ bila proširena kao make promenljiva, dok bi druga izlazna linija bila trenutna vrednost vaše promenljive okruženja $PATH, pošto je funkcija vrednosti izbegavala proširenje.

Funkcija vrednosti se najčešće koristi u sprezi sa funkcijom eval.

Funkcija eval

Funkcija eval omogućava definisanje novih makefile konstrukcija koje nisu konstantne i koje su rezultat vrednovanja drugih promenljivih i funkcija. Argument funkcije eval se proširuje, a zatim se rezultati tog proširenja analiziraju kao sintaksa makefile-a. Prošireni rezultati mogu da definišu nove promenljive, ciljeve, implicitna ili eksplicitna pravila, itd.

Rezultat funkcije eval je uvek prazan string. Na taj način, može se postaviti praktično bilo gde u makefile-u bez izazivanja sintaksičkih grešaka.

Evo primera koji kombinuje niz koncepata i drugih funkcija i ilustruje kako se eval može koristiti:

PROGRAMS = server client

server_OBJS = server.o server_priv.o server_access.o
server_LIBS = priv protocol

client_OBJS = client.o client_api.o client_mem.o
client_LIBS = protocol

.PHONY: all
all: $(PROGRAMS)

define PROGRAM_template =
$(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%)
ALL_OBJS += $$($(1)_OBJS)
endef

$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog))))

$(PROGRAMS):
$(LINK.o) $^ $(LDLIBS) -o $@

clean:
rm -f $(ALL_OBJS) $(PROGRAMS)

Funkcija porekla

Funkcija origin se razlikuje od većine drugih funkcija po tome što ne radi sa vrednostima promenljivih, već govori o njihovom poreklu. Rezultat ove funkcije je string koji govori kako je promenljiva definisana.

$ (origin promenljiva)

Ovde promenljiva predstavlja ime promenljive o kojoj treba da se raspitate, a ne referencu na tu promenljivu.
Mogući rezultati poziva ove funkcije dati su u tabeli:

rezultat značenje
undefined Promenljiva nikada nije bila definisana.
default Promenljiva ima podrazumevanu definiciju, kao što je uobičajeno sa CC i tako dalje. Pogledajte Promenljive koje koriste implicitna pravila. Imajte na umu da ako ste redefinisali podrazumevanu promenljivu, funkcija origin će vratiti poreklo kasnije definicije.
environment Promenljiva je nasleđena iz okruženja predviđenog za izradu.
environment override Promenljiva je nasleđena iz okruženja koje je obezbeđeno za pravljenje i zamenjuje podešavanje za promenljivu u makefileu kao rezultat opcije -e.
file Promenljiva je definisana u makefile-u.
command line Promenljiva je definisana u komandnoj liniji.
override Promenljiva je definisana override direktivom u makefile-u.
automatic Promenljiva je automatska promenljiva.

Funkcija ukusa

Funkcija flavor, kao i funkcija porekla, ne radi sa vrednostima promenljivih, već govori o tipu promenljive. Rezultat ove funkcije je string koji identifikuje tip.

$ (flavor variable)

Ovde promenljiva predstavlja ime promenljive o kojoj se raspitujete, a ne referencu na tu promenljivu.
Mogući rezultati poziva ove funkcije dati su u tabeli

rezultat značenje
undefined Promenljiva nikada nije bila definisana.
recursive promenljiva je rekurzivno proširena promenljiva.
simple Promenljiva je jednostavno proširena promenljiva.

Funkcije koje kontrolišu Make

Ove funkcije kontrolišu način pokretanja. Koriste se za pružanje informacija korisniku makefile-a ili za zaustavljanje make-a ako se otkrije neka vrsta greške u okruženju.

$(error tekst…)

Generiše fatalnu grešku gde je poruka tekstualna. Greška se generiše svaki put kada se proceni ova funkcija. Dakle, ako se koristi u komandama ili na desnoj strani dodeljivanja rekurzivne promenljive, neće biti procenjeno kasnije. Tekst će biti proširen pre nego što se greška generiše.

Na primer,

ifdef ERROR1
$(error error is $(ERROR1))
endif

će generisati fatalnu grešku tokom čitanja makefile-a ako je definisana make promenljiva ERROR1, a

ERR = $(error found an error!)

.PHONY: err
err: ; $(ERR)

će generisati fatalnu grešku dok je make pokrenut, ako se pozove cilj err.

$(warning tekst...)

Ova funkcija radi slično kao i funkcija greške, osim što make ne završava. Umesto toga, tekst se proširuje i rezultujuća poruka se prikazuje, ali se obrada makefila-a nastavlja. Rezultat proširenja ove funkcije je prazan string.

$(info tekst…)

Ova funkcija ne radi ništa više od štampanja svojih (proširenih) argumenta u standardni izlaz. Nije dodat naziv makefile ili broj linije. Rezultat proširenja ove funkcije je prazan string.

Uslovi

Uslovna direktiva uzrokuje da se deo makefile-a poštuje ili ignoriše u zavisnosti od vrednosti promenljivih. Uslovi mogu da uporede vrednost jedne promenljive sa drugom, ili vrednost promenljive sa konstantnim nizom. Uslovi kontrolišu šta make zapravo „vidi“ u makefile-u, pa se ne mogu koristiti za kontrolu komandi u vreme izvršenja.

Primer

Sledeći primer uslova poručuje Make-u da koristi jedan skup biblioteka ako je CC promenljiva gcc, a drugi skup biblioteka u suprotnom. Funkcioniše tako što kontroliše koja od dve linije komandi će se koristiti za pravilo.

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

Ovaj uslov koristi tri direktive: ifeq, else i endif.

  ⇀ Direktiva ifek započinje uslov i specificira uslov. Sadrži dva argumenta, odvojena zarezom i okružena zagradama. Zamena promenljive se vrši za oba argumenta i zatim se upoređuju. Redovi makefile-a koji slede ifeq se poštuju ako se dva argumenta poklapaju, inače se ignorišu.

  ⇀ Else direktiva uzrokuje da se sledeći redovi poštuju ako prethodni uslov nije uspeo. To znači da se druga alternativna komanda povezivanja koristi kad god se prva alternativa ne koristi. Opciono je imati else u uslovu.

  ⇀ Endif direktiva završava uslov. Svaki uslov mora da se završi endif. Sledi bezuslovni makefile tekst.

Sintaksa uslova

Sintaksa jednostavnog uslova bez else-a je sledeća:

  conditional-directive
  text-if-true
  endif
Uslovi utiču na to koje linije makefile-a se koriste. Ako je uslov tačan, make čita redove text-if-true kao deo makefile-a. Ako je uslov netačan, make potpuno ignoriše te linije.

Sintaksa složenog uslova je sledeća:

  conditional-directive
  text-if-true
  else
  text-if-false
  endif
ili
  conditional-directive-one
  text-if-one-is-true
  else conditional-directive-two
  text-if-two-is-true
  else
  text-if-one-and-two-are-false
  endif
Jednom kada je dati uslov istinit, koristi se text-if-true i nijedna druga klauzula se ne koristi. Ako nijedan uslov nije istinit onda se koristi text-if-flase.

Dodatni razmaci su dozvoljeni i zanemareni na početku linije uslovne direktive, ali tab nije dozvoljen. (Ako red počinje tabulatorom, smatraće se delom komandi za pravilo.) Osim toga, dodatni razmaci ili tabulatori mogu biti umetnuti bez efekta bilo gde osim unutar naziva direktive ili unutar argumenta. Komentar koji počinje sa # može se pojaviti na kraju reda.

Uslovne direktive

Postoje četiri različite direktive koje testiraju različite uslove. Date su u sledećoj tabeli:

direktiva upotreba
ifek (arg1, arg2)
ifek 'arg1' 'arg2'
ifek "arg1" "arg2"
ifek "arg1" 'arg2'
ifek 'arg1' "arg2"
Proširuje sve reference promenljivih u arg1 i arg2 i upoređuje ih. Ako su identične, text-if-true je efikasan. U suprotnom, text-if-false, ako postoji, je efikasan.
ifnek (arg1, arg2)
ifnek 'arg1' 'arg2'
ifnek "arg1" "arg2"
ifnek "arg1" 'arg2'
ifnek 'arg1' "arg2"
Proširuje sve reference promenljivih u arg1 i arg2 i upoređuje ih. Ako su različite, text-if-true je efikasan. U suprotnom, text-if-false, ako postoji, je efikasan.
ifdef ime-promenljive Forma ifdef uzima ime-promenljive kao svoj argument, a ne referencu na promenljivu. Ako vrednost te promenljive ima vrednost koja nije prazna, text-if-true je efikasan. U suprotnom, text-if-false, ako postoji, je efikasan.
ifndef ime-promenljive Ako promenljiva ime-promenljive ima praznu vrednost, text-if-true je efikasan. U suprotnom, text-if-false, ako postoji, je efikasan.

Primer 1

Neka je dat jednostavam “Hello World” C program koji se nalazi u hello.c fajlu i samo ispisuje poruku u terminalu:

#include <stdio.h>
int main() {
  printf(“Hello World\n”);
  return 0;
}

Kompilacija u jednom koraku

Makefile bi mogao izgledati ovako:

all : hello
hello : hello.c
  gcc -o hello hello.c
clean :
  rm hello

Pokretanje komande “make” bez argumenata pokreće prvo pravilo na koje se naiđe. U ovom primeru to je “all” koje kao preduslov ima “hello”. Make ne može da pronađe fajl “hello” (barem u prvom pozivu), tako da traži odgovarajuće pravilo kako bi ga kreirao. To pravilo je “hello” koje ima preduslov “hello.c”, koji postoji u direktorijumu. Pokreće se komanda “gcc -o hello hello.c”. U ovom trenutku se dobije “hello” izvršni fajl koji je preduslov za pravilo “all”, te se sada ono može pokrenuti. Budući da ispod ovog pravila ne postoji nijedna komanda, ovo pravilo ne radi ništa. Time se izvršavanje Make-a završava.

Ukoliko ciljna datoteka u nekom pravilu postoji, komanda će se pokrenuti samo u slučaju ako je preduslov noviji od cilj. Drugim rečima, komanda će se pokrenuti samo ako je neki od preduslova menjan od poslednjeg pokretanja make komande. U suprotnom slučaju, nema smisla pokretati naredbu ponovno. Kao povrda toga, ako ponovo pokrenemo “make” komandu dobijamo sledeći ispis:

make: Nothing to be done for “all”
Prilikom pozivanja “make” komande, moguće je specificirati cilj. Na primer, “make clean” uklanja fajl hello. Ukoliko se pokrene “make” bez cilja pozvaće se prvi cilja na koje se naiđe. U ovom primeru, “make all” je isto kao i “make”.

Ukoliko komanda ne počinje tabulatorom, u terminalu se dobija poruka:

makefile:4: *** missing separator.Stop.
Ukoliko ne postoji makefile u trenutnom direktorijum dobija se greska:

make:*** No targets specified and no makefile found. Stop.

Kompilacija i linkovanje

Malo detaljniji makefile mogao bi izgledati ovako:

all: hello
hello: hello.o
  gcc -o hello hello.o
hello.o: hello.c
  gcc -c hello.c
clean:
  rm hello.o hello

Sada će se zasebno kompajlirati i linkovati program. Prvo pravilo na koje se nailazi je “all:hello”, i budući da nema “hello” izvršnog fajla, traži se pravilo za njegovo kreiranje. Pravilo “hello” ima preduslov “hello.o” i pošto ni taj fajl ne postoji, na sličan način se traži pravilo za njega. Pravilo za “hello.o” ima preduslov “hello.c”, koji postoji u direktorijumu. Pokreće se komanda “gcc -c hello.c”. Rekurzivno unazad, pravilo “hello” će dalje da pokrene komandu “gcc -o hello hello.o”, dok pravilo “all” ne radi zapravo ništa. U ovom će poziv “make clean” naredbe izbrisati i objektni fajl “hello.o”.

Lažni ciljevi

Ukoliko je cilj fajl, biće proverena zastarelost njegovih preduslova. Međutim, lažna pravila se uvek izvršavaju. Standardna lažna pravila su “all”, “clean” i “install”. Kako bi se naznačilo da je neki cilj lažan koristi se oznaka “.PHONY” neposredno pre pravila. U ovom primeru “clean” bi mogao da bude lažni cilj:

.PHONY: all
all: hello
hello: hello.o
  gcc -o hello hello.o
hello.o: hello.c
  gcc -c hello.c

.PHONY: clean
clean:
  rm hello.o hello

Automatke promenljive

Možemo napraviti ekvivalentan makefile koristeći automatske promenljive:

.PHONY: all
all: hello
hello: hello.o
  gcc -o $@ $<
hello.o: hello.c
  gcc -c $<
.PHONY: clean
clean:
  rm hello.o hello

Primer 2

Dat je jednostavan program pomoću tri fajla:

1. calculate.c
#include "helper.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main (int argc,char** argv) {
  int a = atoi(argv[1]);
  int b = atoi(argv[2]);
  int res = pomnozi(a,b);
  printf("Rezultat mnozenja je: %d\n",res);
}

2. helper.h
#ifndef HELPER_H
#define HELPER_H

extern int pomnozi(int, int);

#endif

3. helper.c
#include "helper.h"

int pomnozi(int a, int b) {
  return b*a;
}

U fajlu calculate.c nalazi se main funkcija koja prima parametre prilikom njenog poziva. Parametri se parsiraju u intiger brojeve a zatim se nad njima poziva funkcija pomnozi() i rezultat se ispisuje u terminalu. Funkcija ima svoju deklaraciju u “helper.h” fajlu, a svoju definiciju u “helper.c” fajlu. Potrebno je napraviti makefile koji kompajlira prethodni program u objektne fajlove a zatim ih linkuje u izvršni. Ukoliko se neki od izvornih fajlova promeni, makefile treba da rekompajlira sve fajlove na koje on utiče. Proces kompajliranja u ovom slučaju je prikazan na sledećoj slici:

pr2

Makefile bi mogao da izgleda ovako:

all: calculate
calculate: calculate.o helper.o
  gcc -o calculate calculate.o helper.o
calculate.o: calculate.c helper.h
  gcc -c calculate.c
helper.o: helper.c helper.h
  gcc -c helper.c
clean:
  rm -f calculate *.o

Primer 3

Evo jednostavnog makefile-a koji opisuje način na koji izvršna datoteka “edit”. Ona zavisi od osam objektnih datoteka koje zauzvrat zavise od osam C izvornih i tri datoteke zaglavlja.

Sve C datoteke uključuju “defs.h”, ali samo one koje definišu komande za uređivanje uključuju “command.h”, a samo datoteke niskog nivoa koje menjaju bafer uređivača uključuju “buffer.h”.

edit : main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o
  cc -o edit main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

main.o : main.c defs.h
  cc -c main.c
kbd.o : kbd.c defs.h command.h
  cc -c kbd.c
command.o : command.c defs.h command.h
  cc -c command.c
display.o : display.c defs.h buffer.h
  cc -c display.c
insert.o : insert.c defs.h buffer.h
  cc -c insert.c
search.o : search.c defs.h buffer.h
  cc -c search.c
files.o : files.c defs.h buffer.h command.h
  cc -c files.c
utils.o : utils.c defs.h
  cc -c utils.c
clean :
  rm edit main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o

Da biste koristili ovaj makefile za kreiranje izvršne datoteke pod nazivom edit otkucajte:

$ make
Da biste koristili ovaj makefile za brisanje izvršne datoteke i svih objektnih datoteka iz direktorijuma otkucajte:

$ make clean
Cilj “clean”-a nije datoteka, već samo ime radnje. Pošto obično ne želite da izvršite radnje u ovom pravilu, “clean” nije preduslov za bilo koje drugo pravilo. Shodno tome, Make nikada ne radi ništa sa tim osim ako to posebno ne kažete. Takođe, “clean” nema nikakve preduslove.

Make čita makefile u trenutnom direktorijumu i počinje obradom prvog pravila. U primeru, ovo pravilo je za ponovno povezivanje izmene, ali pre nego što make može u potpunosti da obradi ovo pravilo, mora da obradi pravila za datoteke od kojih zavisi uređivanje, a to su u ovom slučaju objektne datoteke. Svaki od ovih fajlova se obrađuje prema sopstvenom pravilu. Ova pravila kažu da se ažurira svaki .o fajl prevođenjem njegovog izvornog fajla. Ponovna kompilacija se mora obaviti ako je izvorna datoteka, ili bilo koja od datoteka zaglavlja nazvana kao preduslovi, novija od objektne datoteke, ili ako objektna datoteka ne postoji.

Ostala pravila se obrađuju jer se njihovi ciljevi pojavljuju kao preduslovi cilja. Ako cilj ne zavisi od nekog drugog pravila, to pravilo se ne obrađuje, osim ako ne kažete make-u da to uradi.

Morali smo da navedemo sve objektne datoteke dva puta u pravilu za uređivanje:

edit : main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o
  cc -o edit main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o

Takvo dupliranje je sklono greškama. Ako se u sistem doda nova objektna datoteka, možemo je dodati na jednu listu i zaboraviti da je dodamo na drugu.

Možemo eliminisati rizik i pojednostaviti makefile korišćenjem promenljive objects. Na svako mesto na koje želimo da stavimo listu naziva fajlova objekata, možemo da zamenimo vrednost promenljive pisanjem $(objects):

objects = main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o

Evo kako izgleda kompletan jednostavan makefile kada koristite promenljivu za objektne datoteke:

objects = main.o kbd.o command.o display.o \
  insert.o search.o files.o utils.o

edit : $(objects)
  cc -o edit $(objects)
main.o : main.c defs.h
  cc -c main.c
kbd.o : kbd.c defs.h command.h
  cc -c kbd.c
command.o : command.c defs.h command.h
  cc -c command.c
display.o : display.c defs.h buffer.h
  cc -c display.c
insert.o : insert.c defs.h buffer.h
  cc -c insert.c
search.o : search.c defs.h buffer.h
  cc -c search.c
files.o : files.c defs.h buffer.h command.h
  cc -c files.c
utils.o : utils.c defs.h
  cc -c utils.c
clean :
   rm edit $(objects)