Faceți fișiere. Construirea unui program cu diferiți parametri de compilare. Sintaxa make de bază

make este un utilitar pentru asamblare automată programe. Vă permite să urmăriți modificările în cod sursa program și nu compilați întregul proiect, ci doar acele fișiere care s-au modificat sau de care depind modificările efectuate. La proiecte mari aceasta oferă economii semnificative de timp.

În această postare voi încerca să vă spun cum să creați un makefile.

În mod implicit, regulile de construire sunt citite dintr-un fișier numit Makefile.

Structura Makefile poate fi reprezentată după cum urmează:

SCOP: ACȚIUNEA DE DEPENDENTĂ

Dar de obicei se folosesc reguli mai complexe, de exemplu:

GOAL: GOAL1 GOAL2 ACTION GOAL1: DEPENDENCE1 ACTION1 GOAL2: DEPENDENCE2 ACTION2

GOAL este ceea ce obținem ca rezultat al ACȚIUNII. Acesta poate fi un fișier, un director sau pur și simplu un TARGET abstract care nu are nicio legătură cu niciun obiect de pe hard disk. După numele țintă sunt plasate două puncte. Când rulați comanda make fără parametri, prima regulă găsită va fi executată. Pentru a executa o altă regulă, trebuie să o specificați la comanda make

Faceți GOAL2

DEPENDENȚA este de care depinde OBIECTUL nostru. Acestea pot fi fișiere, directoare sau alte ȚINTE. Make compară data și ora modificării GOAL și obiectele de care depinde obiectivul. Dacă obiectele de care depinde obiectivul au fost modificate mai târziu decât a fost creat scopul, atunci va fi executată o ACȚIUNE. ACTION este, de asemenea, efectuată dacă TARGET nu este un nume de fișier sau director.

O ACȚIUNE este un set de comenzi care trebuie executate. Comenzile trebuie să fie precedate de un caracter tabulator. Dacă se introduc spații în loc de caracterul tabulator, va fi afișat un mesaj de eroare în timpul compilării:

Makefile:13: ***lipsește delimitătorul. Stop.

Makefile:13: *** lipsește separatorul. Stop.

Exemplu de makefile:

toate: test.elf test.elf: test1.o test2.o gcc -o test.elf test1.o test2.o test1.o test1.c gcc -c test1.c -o test1.o test2.o test2.c gcc -c test2.c -o test2.o

Luați în considerare ultimul exemplu:
Totul este executat mai întâi pentru că este la începutul Makefile. totul depinde de test.elf și nu există niciun fișier sau director numit all, așa că va verifica întotdeauna ținta numită test.elf.

test.elf depinde de test1.o și test2.o, deci testul țintă1.o va fi verificat mai întâi, apoi test2.o

La verificarea țintă test1.o, se compară data și ora modificării fișierului test1.o și test1.c. Dacă fișierul test1.o nu există sau fișierul test1.c a fost modificat mai târziu decât test1.o, atunci comanda gcc -c test1.c -o test1.o va fi executată.

Testul țintă2.o va fi verificat în același mod.

După aceasta, data și ora modificării fișierului test.elf și fișierele test1.o test2.o sunt comparate. Dacă test1.o sau test2.o este mai nou, atunci comanda gcc -o test.elf test1.o test2.o va fi executată

În acest fel, modificările din fișierele test1.c și test2.c sunt urmărite.

P/S Sper că această notă va simplifica crearea unui makefile, dar dacă aveți întrebări, scrieți în comentarii, voi încerca să vă răspund.

Utilitarul determină automat ce piese program mare comenzile pentru a le recompila trebuie recompilate. Cel mai adesea face folosit pentru compilarea programelor C și conține caracteristici care vizează în mod special astfel de sarcini, dar puteți utiliza face cu orice limbaj de programare. Mai mult, folosind utilitarul face nu se limitează la programe. Îl puteți folosi pentru a descrie orice problemă în care unele fișiere trebuie să fie generate automat de la altele ori de câte ori se schimbă.

make-file

Inainte de folosire face, trebuie să creați un fișier numit makefile, care descrie relațiile dintre fișierele programului și conține comenzi pentru a actualiza fiecare fișier. De obicei fisier executabil depinde de fișierele obiect, care la rândul lor depind de fișiere sursăși fișiere antet. Pentru nume makefile titlu recomandat GNUmakefile, makefile sau Makefile, și căutarea este în curs exact în ordinea enumerată. Dacă trebuie să utilizați un nume non-standard, îl puteți transmite în mod explicit folosind opțiunea -f.
Când makefile a fost deja scris, trebuie doar să rulați comanda în directorul în care se află face. Simplu makefile constă în reguli (instrucțiuni) de următorul tip:


VARIABIL = VALOARE...
GOOL...: DEPENDENTA...
ECHIPA 1
ECHIPA 2
VARIABIL = VALOARE...
GOOL...: DEPENDENTA...
ECHIPA 1
ECHIPA 2

etc.

ŢINTĂ de obicei reprezintă numele fișierului generat de program face; exemple de ținte sunt fișierele executabile sau obiect. Ținta poate fi, de asemenea, numele acțiunii de efectuat, cum ar fi „ curat".
Dependenta este un fișier a cărui modificare servește ca un indiciu că ținta este necesară. Adesea, ținta depinde de mai multe fișiere.
ECHIPĂ- este o acţiune care realizează face. O regulă poate avea mai multe comenzi, fiecare pe linia sa. Notă importantă: Trebuie să începeți fiecare linie care conține comenzi cu un caracter tabulator. Linii lungi sunt împărțite în mai multe utilizări backslash urmată de o nouă linie. Semn ascuțit # este începutul unui comentariu. Linia cu # complet ignorat. Comentariile se pot întinde pe mai multe linii folosind o bară oblică inversă la sfârșitul rândului.

Exemplu de makefile

Utilizarea acțiunilor implicite


#țintă implicită - editarea fișierului
editați: main.o kbd.o command.o display.o \
cc -o edit main.o kbd.o command.o display.o \
inserați.o căutați.o fișierele.o utils.o

Main.o: main.c defs.h
cc -c main.c
kbd.o: kbd.c defs.h comanda.h
cc -c kbd.c
command.o: command.c defs.h command.h
comanda cc -c.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 căutare.c
files.o: files.c defs.h buffer.h command.h
fișiere cc -c.c
utils.o: utils.c defs.h
cc -c utils.c
curat:
rm edit main.o kbd.o command.o display.o \
inserați.o căutați.o fișierele.o utils.o

Mod implicit, faceîncepe cu prima regulă (fără a număra regulile ale căror nume țintă încep cu " . "). Se numeste scopul principal Mod implicit. În cazul nostru, aceasta este regula Editați | ×. Dacă dosarul Editați | × mai nou decât fișierele obiect de care depinde, atunci nu se va întâmpla nimic. Altfel, înainte face să poată procesa pe deplin această regulă, trebuie să proceseze recursiv regulile pentru fișierele de care depinde " Editați | ×". Fiecare dintre aceste fișiere este procesat conform propriei reguli. Recopilarea trebuie efectuată dacă fișierul sursă sau oricare dintre fișierele antet, menționat printre dependențe, este actualizat mai târziu decât fișierul obiect sau dacă fișierul obiect nu există.
Regulă curat nu corespunde cu niciuna fișier creatși în mod corespunzător, curat Nu depinde de nimic și nu este inclus în lista de dependențe. Când este lansat implicit curat nu va fi chemat. Pentru a-l executa, trebuie să specificați în mod explicit ținta la pornire face - face curat.
Variabilele și acțiunile implicite (reguli implicite) pot fi utilizate pentru a scurta intrarea

Motiv special .FALS este încorporat în faceși își definește dependențele ca nume țintă care nu au fișiere corespunzătoare. Dacă această regulă skip, apoi creați un fișier în directorul curent cu numele curat va bloca execuția face curat.
Utilizarea regulilor implicite vă permite să schimbați stilul intrărilor de dependență:

Parantezele pătrate înseamnă că prezența acestei părți este opțională.
Ţintă- denumirea obiectivului de realizat.
Variabila ="abc"-redefinirea variabilelor. Valorile variabilelor introduse Linie de comanda au prioritate mai mare decât definițiile din makefile.
Opțiuni:
-f fișier- setarea numelui explicit makefile, dacă sarcina este omisă, atunci fișierele sunt căutate GNUmakefile, makefile sau Makefile
-n; - imitarea acțiunilor fără execuție reală, folosită pentru depanare
-t- modificarea timpului de modificare a obiectivului fără executarea efectivă
-q- verificarea necesității actualizării obiectivului fără a-l executa efectiv

Mereu am fost atras de minimalism. Ideea că un lucru ar trebui să facă un lucru, dar să o facă cât mai bine posibil, a dus la crearea UNIX. Și deși UNIX nu mai poate fi numit un sistem simplu, iar minimalismul din el nu este atât de ușor de văzut, poate fi considerat un exemplu clar transformarea cantitativ-calitativă a multor lucruri simple și de înțeles într-unul foarte complex și opac. În dezvoltarea sa, make a parcurs aproximativ aceeași cale: simplitatea și claritatea, cu o amploare tot mai mare, s-au transformat într-un monstru teribil (amintește-ți sentimentele tale când ai deschis prima dată un makefile).

Ignorarea mea persistentă a make-ului pentru o lungă perioadă de timp s-a datorat comodității IDE-urilor utilizate și reticenței de a înțelege această „relicvă a trecutului” (în esență lene). Cu toate acestea, toate aceste butoane enervante, meniuri etc. atributele de tot felul de studiouri m-au obligat să caut o alternativă la metoda de lucru pe care o practicasem până acum. Nu, nu am devenit un guru al creației, dar cunoștințele pe care le-am dobândit sunt destul de suficiente pentru micile mele proiecte. Acest articol este destinat celor care, ca mine tocmai recent, doresc să iasă din sclavia confortabilă a ferestrei în lumea ascetică, dar liberă a cochiliei.

Faceți- informații de bază

make este un utilitar conceput pentru a automatiza conversia fișierelor dintr-un formular în altul. Regulile de conversie sunt specificate într-un script numit Makefile, care trebuie să fie localizat în rădăcina directorului de lucru al proiectului. Scriptul în sine constă dintr-un set de reguli, care la rândul lor sunt descrise:

1) obiective (ce face această regulă);
2) detalii (ceea ce este necesar pentru a îndeplini regula și a obține scopuri);
3) comenzi (realizarea acestor transformări).

ÎN vedere generala Sintaxa makefile poate fi reprezentată după cum urmează:

# Indentarea se realizează exclusiv folosind caractere tabulatoare, # fiecare comandă trebuie să fie precedată de o indentare<цели>: <реквизиты> <команда #1> ... <команда #n>

Adică, regula make este răspunsul la trei întrebări:

(Din ce o facem? (detalii)) ---> [Cum o facem? (comenzi)] ---> (Ce facem? (obiective))
Este ușor de observat că procesele de traducere și compilare se potrivesc foarte bine în această diagramă:

(fișiere sursă) ---> [difuzare] ---> (fișiere obiect)
(fișiere obiect) ---> [link] ---> (fișiere executabile)

Cel mai simplu Makefile

Să presupunem că avem un program format dintr-un singur fișier:

/* * main.c */ #include int main() ( printf("Bună ziua!\n"); return 0; )
Pentru a-l compila, este suficient un makefile foarte simplu:

Salut: main.c gcc -o salut main.c
Acest Makefile constă dintr-o regulă, care, la rândul său, constă dintr-o țintă - „hello”, o prop - „main.c” și o comandă - „gcc -o hello main.c”. Acum, pentru a compila, trebuie doar să lansați comanda make în directorul de lucru. În mod implicit, make va executa prima regulă dacă ținta de execuție nu a fost specificată în mod explicit atunci când este apelată:

$facă<цель>

Compilare din mai multe surse

Să presupunem că avem un program format din 2 fișiere:
principal.c
/* * main.c */ int main() ( salut(); return 0; )
și salut.c
/* * salut.c */ #include void hello() ( printf("Bună ziua!\n"); )
Makefile-ul care compilează acest program ar putea arăta astfel:

Salut: main.c salut.c gcc -o salut main.c salut.c
Este destul de funcțional, dar are un dezavantaj semnificativ: vom dezvălui care dintre ele în continuare.

Compilare incrementală

Să ne imaginăm că programul nostru constă dintr-o duzină sau două fișiere sursă. Facem modificări uneia dintre ele și dorim să o reconstruim. Folosind abordarea descrisă în exemplul anterior, toate fișierele sursă vor fi compilate din nou, ceea ce va avea un impact negativ asupra timpului de recompilare. Soluția este împărțirea compilației în două etape: etapa de traducere și etapa de legătură.

Acum, după schimbarea unuia dintre fișierele sursă, este suficient să îl traduceți și să legați toate fișierele obiect. În același timp, sărim peste etapa de traducere a detaliilor care nu sunt afectate de modificări, ceea ce reduce timpul de compilare în general. Această abordare se numește compilare incrementală. Pentru a sprijini acest lucru, comparați timpii de schimbare a țintelor și detaliile acestora (folosind date Sistemul de fișiere), datorită căruia decide în mod independent ce reguli trebuie urmate și care pot fi pur și simplu ignorate:

Main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c hello: main.o hello.o gcc -o hello main. o buna.o
Încercați să construiți acest proiect. Pentru a-l construi, trebuie să specificați în mod explicit ținta, de exemplu. dă comanda salută.
După aceea, schimbați oricare dintre fișierele sursă și construiți-l din nou. Vă rugăm să rețineți că în timpul celei de-a doua compilații, doar fișierul modificat va fi tradus.

Odată rulat, make va încerca să obțină imediat ținta hello, dar pentru a o crea aveți nevoie de fișierele main.o și hello.o, care nu există încă. Prin urmare, executarea regulii va fi amânată și va căuta reguli care descriu modul de obținere a detaliilor lipsă. Odată ce toate detaliile au fost primite, make va reveni la executarea țintei amânate. Aceasta înseamnă că make execută regulile recursiv.

Ținte fictive

De fapt, nu numai fișierele reale pot acționa ca ținte. Oricine a trebuit să construiască programe din codul sursă ar trebui să fie familiarizat cu două comenzi standard din lumea UNIX:

$ make $ make install
Comanda make compilează programul, iar comanda make install îl instalează. Această abordare este foarte convenabilă, deoarece tot ce este necesar pentru a construi și implementa aplicația în sistem țintă incluse într-un singur fișier (să uităm pentru un moment de scriptul de configurare). Vă rugăm să rețineți că în primul caz nu specificăm scopul, iar în al doilea obiectivul nu este deloc crearea fișierului de instalare, ci procesul de instalare a aplicației pe sistem. Așa-numitele ținte fictive (false) ne permit să realizăm astfel de trucuri. Aici lista finaliștilor obiective standard:

  • totul este ținta implicită. Nu trebuie să specificați în mod explicit atunci când apelați make.
  • curățare - ștergeți directorul cu toate fișierele obținute ca urmare a compilării.
  • instala - execută instalarea
  • dezinstalare - și dezinstalare în consecință.
Pentru a împiedica make-ul să caute fișiere cu astfel de nume, acestea ar trebui să fie definite în Makefile folosind directiva .PHONY. Următorul este un exemplu de Makefile cu toate țintele, curățați, instalați și dezinstalați:

PHONY: all clean install uninstall all: hello clean: rm -rf hello *.o main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o salut. o hello.c hello: main.o hello.o gcc -o hello main.o hello.o install: install ./hello /usr/local/bin dezinstalare: rm -rf /usr/local/bin/hello
Acum putem să ne construim programul, să-l instalăm/dezinstalăm și, de asemenea, să curățăm directorul de lucru folosind ținte standard.

Rețineți că nu există comenzi specificate în toate ținta; tot ce are nevoie este să obțină recuzita. Știind natura recursivă a make-ului, nu este greu de imaginat cum va funcționa acest script. De asemenea, trebuie remarcat Atentie speciala pe faptul că dacă fișierul hello există deja (rămas după compilarea anterioară) și detaliile acestuia nu au fost modificate, atunci comanda make nimic nu va fi reasamblat. Acesta este o grebla clasica. De exemplu, schimbând un fișier antet care nu a fost inclus accidental în lista de detalii, puteți obține ore lungi durere de cap. Prin urmare, pentru a garanta o reconstrucție completă a proiectului, trebuie mai întâi să ștergeți directorul de lucru:

$ face curat $ face
Va trebui să utilizați sudo pentru a finaliza obiectivele de instalare/dezinstalare.

Variabile

Toți cei care sunt familiarizați cu regula DRY (Nu te repeta) probabil că au observat deja ceva în neregulă, și anume, Makefile-ul nostru conține număr mare fragmente repetate, ceea ce poate duce la confuzie în timpul încercărilor ulterioare de extindere sau modificare. În limbajele imperative, avem variabile și constante pentru aceste scopuri; make are și facilități similare. Variabilele din make sunt numite șiruri și sunt definite foarte simplu:

=
Există o regulă nerostită conform căreia variabilele ar trebui să fie numite cu litere mari, de exemplu:

SRC = main.c salut.c
Așa am definit lista fișierelor sursă. Pentru a utiliza valoarea unei variabile, aceasta trebuie dereferențiată folosind constructul $( ); de exemplu cam asa:

Gcc -o salut $(SRC)
Mai jos este un makefile care folosește două variabile: TARGET - pentru a determina numele programul țintăși PREFIX - pentru a determina calea de instalare a programului pe sistem.

TARGET = salut PREFIX = /usr/local/bin .PHONY: all clean install uninstall all: $(TARGET) clean: rm -rf $(TARGET) *.o main.o: main.c gcc -c -o main. o main.c hello.o: hello.c gcc -c -o hello.o hello.c $(TARGET): main.o hello.o gcc -o $(TARGET) main.o hello.o instalați: instalați $ (TARGET) $(PREFIX) dezinstalare: rm -rf $(PREFIX)/$(TARGET)
Acesta este deja mai frumos. Cred că acum exemplul de mai sus nu are nevoie de comentarii speciale pentru tine.

Variabile automate

Variabilele automate sunt menite să simplifice fișierele make, dar în opinia mea au un impact negativ asupra lizibilității lor. Oricum ar fi, voi enumera aici câteva dintre variabilele cele mai frecvent utilizate și ce să faci cu ele (sau dacă să o faci deloc) depinde de tine:
  • $@ Numele țintă al regulii care este procesată
  • $< Имя первой зависимости обрабатываемого правила
  • $^ Lista tuturor dependențelor regulii procesate
Dacă cineva dorește să-și ascundă complet scripturile, vă puteți inspira aici:

Mereu am fost atras de minimalism. Ideea că un lucru ar trebui să facă un lucru, dar să o facă cât mai bine posibil, a dus la crearea UNIX. Și deși UNIX nu mai poate fi numit un sistem simplu, iar minimalismul în el nu este atât de ușor de văzut, poate fi considerat un exemplu clar al transformării cantitative și calitative a multor lucruri simple și de înțeles într-unul foarte complex și opac. În dezvoltarea sa, make a parcurs aproximativ aceeași cale: simplitatea și claritatea, cu o amploare tot mai mare, s-au transformat într-un monstru teribil (amintește-ți sentimentele tale când ai deschis prima dată un makefile).

Ignorarea mea persistentă a make-ului pentru o lungă perioadă de timp s-a datorat comodității IDE-urilor utilizate și reticenței de a înțelege această „relicvă a trecutului” (în esență lene). Cu toate acestea, toate aceste butoane enervante, meniuri etc. atributele de tot felul de studiouri m-au obligat să caut o alternativă la metoda de lucru pe care o practicasem până acum. Nu, nu am devenit un guru al creației, dar cunoștințele pe care le-am dobândit sunt destul de suficiente pentru micile mele proiecte. Acest articol este destinat celor care, ca mine tocmai recent, doresc să iasă din sclavia confortabilă a ferestrei în lumea ascetică, dar liberă a cochiliei.

Faceți- informații de bază

make este un utilitar conceput pentru a automatiza conversia fișierelor dintr-un formular în altul. Regulile de conversie sunt specificate într-un script numit Makefile, care trebuie să fie localizat în rădăcina directorului de lucru al proiectului. Scriptul în sine constă dintr-un set de reguli, care la rândul lor sunt descrise:

1) obiective (ce face această regulă);
2) detalii (ceea ce este necesar pentru a îndeplini regula și a obține scopuri);
3) comenzi (realizarea acestor transformări).

În general, sintaxa makefile poate fi reprezentată după cum urmează:

# Indentarea se realizează exclusiv folosind caractere tabulatoare, # fiecare comandă trebuie să fie precedată de o indentare<цели>: <реквизиты> <команда #1> ... <команда #n>

Adică, regula make este răspunsul la trei întrebări:

(Din ce o facem? (detalii)) ---> [Cum o facem? (comenzi)] ---> (Ce facem? (obiective))
Este ușor de observat că procesele de traducere și compilare se potrivesc foarte bine în această diagramă:

(fișiere sursă) ---> [difuzare] ---> (fișiere obiect)
(fișiere obiect) ---> [link] ---> (fișiere executabile)

Cel mai simplu Makefile

Să presupunem că avem un program format dintr-un singur fișier:

/* * main.c */ #include int main() ( printf("Bună ziua!\n"); return 0; )
Pentru a-l compila, este suficient un makefile foarte simplu:

Salut: main.c gcc -o salut main.c
Acest Makefile constă dintr-o regulă, care, la rândul său, constă dintr-o țintă - „hello”, o prop - „main.c” și o comandă - „gcc -o hello main.c”. Acum, pentru a compila, trebuie doar să lansați comanda make în directorul de lucru. În mod implicit, make va executa prima regulă dacă ținta de execuție nu a fost specificată în mod explicit atunci când este apelată:

$facă<цель>

Compilare din mai multe surse

Să presupunem că avem un program format din 2 fișiere:
principal.c
/* * main.c */ int main() ( salut(); return 0; )
și salut.c
/* * salut.c */ #include void hello() ( printf("Bună ziua!\n"); )
Makefile-ul care compilează acest program ar putea arăta astfel:

Salut: main.c salut.c gcc -o salut main.c salut.c
Este destul de funcțional, dar are un dezavantaj semnificativ: vom dezvălui care dintre ele în continuare.

Compilare incrementală

Să ne imaginăm că programul nostru constă dintr-o duzină sau două fișiere sursă. Facem modificări uneia dintre ele și dorim să o reconstruim. Folosind abordarea descrisă în exemplul anterior, toate fișierele sursă vor fi compilate din nou, ceea ce va avea un impact negativ asupra timpului de recompilare. Soluția este împărțirea compilației în două etape: etapa de traducere și etapa de legătură.

Acum, după schimbarea unuia dintre fișierele sursă, este suficient să îl traduceți și să legați toate fișierele obiect. În același timp, sărim peste etapa de traducere a detaliilor care nu sunt afectate de modificări, ceea ce reduce timpul de compilare în general. Această abordare se numește compilare incrementală. Pentru a-l susține, comparați timpul de schimbare a țintelor și detaliile acestora (folosind datele sistemului de fișiere), datorită cărora decide independent ce reguli trebuie executate și care pot fi pur și simplu ignorate:

Main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o hello.o hello.c hello: main.o hello.o gcc -o hello main. o buna.o
Încercați să construiți acest proiect. Pentru a-l construi, trebuie să specificați în mod explicit ținta, de exemplu. dă comanda salută.
După aceea, schimbați oricare dintre fișierele sursă și construiți-l din nou. Vă rugăm să rețineți că în timpul celei de-a doua compilații, doar fișierul modificat va fi tradus.

Odată rulat, make va încerca să obțină imediat ținta hello, dar pentru a o crea aveți nevoie de fișierele main.o și hello.o, care nu există încă. Prin urmare, executarea regulii va fi amânată și va căuta reguli care descriu modul de obținere a detaliilor lipsă. Odată ce toate detaliile au fost primite, make va reveni la executarea țintei amânate. Aceasta înseamnă că make execută regulile recursiv.

Ținte fictive

De fapt, nu numai fișierele reale pot acționa ca ținte. Oricine a trebuit să construiască programe din codul sursă ar trebui să fie familiarizat cu două comenzi standard din lumea UNIX:

$ make $ make install
Comanda make compilează programul, iar comanda make install îl instalează. Această abordare este foarte convenabilă deoarece tot ceea ce este necesar pentru construirea și implementarea aplicației pe sistemul țintă este inclus într-un singur fișier (să uităm pentru un moment de scriptul de configurare). Vă rugăm să rețineți că în primul caz nu specificăm scopul, iar în al doilea obiectivul nu este deloc crearea fișierului de instalare, ci procesul de instalare a aplicației pe sistem. Așa-numitele ținte fictive (false) ne permit să realizăm astfel de trucuri. Iată o scurtă listă de obiective standard:

  • totul este ținta implicită. Nu trebuie să specificați în mod explicit atunci când apelați make.
  • curățare - ștergeți directorul cu toate fișierele obținute ca urmare a compilării.
  • instala - execută instalarea
  • dezinstalare - și dezinstalare în consecință.
Pentru a împiedica make-ul să caute fișiere cu astfel de nume, acestea ar trebui să fie definite în Makefile folosind directiva .PHONY. Următorul este un exemplu de Makefile cu toate țintele, curățați, instalați și dezinstalați:

PHONY: all clean install uninstall all: hello clean: rm -rf hello *.o main.o: main.c gcc -c -o main.o main.c hello.o: hello.c gcc -c -o salut. o hello.c hello: main.o hello.o gcc -o hello main.o hello.o install: install ./hello /usr/local/bin dezinstalare: rm -rf /usr/local/bin/hello
Acum putem să ne construim programul, să-l instalăm/dezinstalăm și, de asemenea, să curățăm directorul de lucru folosind ținte standard.

Rețineți că nu există comenzi specificate în toate ținta; tot ce are nevoie este să obțină recuzita. Știind natura recursivă a make-ului, nu este dificil să ne imaginăm cum va funcționa acest script. De asemenea, ar trebui să acordați o atenție deosebită faptului că, dacă fișierul hello există deja (rămas după compilarea anterioară) și detaliile acestuia nu au fost modificate, atunci comanda make nimic nu va fi reasamblat. Acesta este o grebla clasica. De exemplu, schimbarea unui fișier antet care nu este inclus accidental în lista de detalii poate duce la multe ore de dureri de cap. Prin urmare, pentru a garanta o reconstrucție completă a proiectului, trebuie mai întâi să ștergeți directorul de lucru:

$ face curat $ face
Va trebui să utilizați sudo pentru a finaliza obiectivele de instalare/dezinstalare.

Variabile

Toți cei care sunt familiarizați cu regula DRY (Nu te repeta) probabil au observat deja că ceva nu este în regulă, și anume, Makefile-ul nostru conține un număr mare de fragmente repetate, ceea ce poate duce la confuzie în încercările ulterioare de a-l extinde sau de a-l schimba. În limbaje imperative pentru acestea Avem variabile și constante pentru scopurile noastre, iar make are facilități similare. Variabilele din make sunt numite șiruri și sunt definite foarte simplu:

=
Există o regulă nerostită conform căreia variabilele ar trebui să fie numite cu litere mari, de exemplu:

SRC = main.c salut.c
Așa am definit lista fișierelor sursă. Pentru a utiliza valoarea unei variabile, aceasta trebuie dereferențiată folosind constructul $( ); de exemplu cam asa:

Gcc -o salut $(SRC)
Mai jos este un makefile care folosește două variabile: TARGET - pentru a determina numele programului țintă și PREFIX - pentru a determina calea de instalare a programului pe sistem.

TARGET = salut PREFIX = /usr/local/bin .PHONY: all clean install uninstall all: $(TARGET) clean: rm -rf $(TARGET) *.o main.o: main.c gcc -c -o main. o main.c hello.o: hello.c gcc -c -o hello.o hello.c $(TARGET): main.o hello.o gcc -o $(TARGET) main.o hello.o instalați: instalați $ (TARGET) $(PREFIX) dezinstalare: rm -rf $(PREFIX)/$(TARGET)
Acesta este deja mai frumos. Cred că acum exemplul de mai sus nu are nevoie de comentarii speciale pentru tine.

Variabile automate

Variabilele automate sunt menite să simplifice fișierele make, dar în opinia mea au un impact negativ asupra lizibilității lor. Oricum ar fi, voi enumera aici câteva dintre variabilele cele mai frecvent utilizate și ce să faci cu ele (sau dacă să o faci deloc) depinde de tine:
  • $@ Numele țintă al regulii care este procesată
  • $< Имя первой зависимости обрабатываемого правила
  • $^ Lista tuturor dependențelor regulii procesate
Dacă cineva dorește să-și ascundă complet scripturile, vă puteți inspira aici:

Acest articol este un scurt tutorial despre crearea Makefiles. Acesta explică pentru ce este un Makefile și oferă câteva reguli de urmat atunci când creați unul.

Introducere

Să presupunem că dezvoltați un program numit foo, format din cinci fișiere antet - 1.h, 2.h, 3.h, 4.h și - 5.h și șase fișiere cu codul sursă al programului în C - 1.cpp, 2.cpp, 3.cpp, 4.cpp, 5.cpp și main.cpp. (Aș dori să observ că în proiectele reale acest stil de denumire a fișierelor ar trebui evitat.)

Acum să ne imaginăm că ați descoperit o eroare în fișierul 2.cpp și ați remediat-o. Apoi, pentru a obține o versiune corectată a programului, compilați toate fișierele incluse în proiect, deși modificările au afectat doar un fișier. Acest lucru duce la pierdere de timp, mai ales dacă computerul nu este foarte rapid.

Există o soluție la problemă?

Nu trebuie să vă faceți griji, prieteni! Această problemă a fost rezolvată cu mult timp în urmă. Programatori cu experiență au dezvoltat utilitarul make. În loc să recompilați toate fișierele cu textele sursă, procesează numai acele fișiere care au suferit modificări. În cazul nostru, va fi compilat un singur fișier - 2.cpp. Nu este grozav!?

  • Utilitarul make face viața mult mai ușoară atunci când trebuie să rulați comenzi lungi și complexe pentru a construi un proiect.
  • Un proiect necesită uneori setarea opțiunilor compilatorului rar utilizate și, prin urmare, greu de reținut. make vă va scuti de osteneala de a le păstra în memorie.
  • Uniformitate, pentru că lucrul cu acest utilitar este suportat de multe medii de dezvoltare.
  • Procesul de construire poate fi automatizat deoarece make poate fi apelat din scripturi sau din cron.

Pentru ce este Makefile-ul?

În ciuda tuturor avantajelor sale, utilitarul make nu știe nimic despre proiectul nostru, așa că trebuie să creăm un simplu fisier text care va contine totul instructiunile necesare la asamblare. Se numește fișierul cu instrucțiuni pentru asamblarea proiectului makefile (pronunțat „makefile”)aproximativ traducere) .

De obicei, aceste fișiere sunt denumite makefile sau Makefile, urmând convențiile de denumire pentru astfel de fișiere. Dacă dați fișierului de instrucțiuni un alt nume, va trebui să apelați make cu opțiunea -f.

De exemplu, dacă ați denumit fișierul makefile bejo , atunci comanda pentru a construi proiectul va arăta astfel:

Make -f bejo

Structura fișierului

Makefile conține secțiuni pentru "goluri", dependențeȘi reguli ansambluri. Toate acestea sunt formatate după cum urmează: mai întâi este specificat numele țintei (de obicei numele fișierului executabil sau obiect), urmat de două puncte, apoi numele dependențelor, adică. fișierele necesare atingerii acestui scop. În cele din urmă, există o listă de reguli: i.e. comenzi care trebuie executate pentru a obține scopul specificat.

Un exemplu simplu de structură makefile:

Țintă: comanda de comandă dependențe...

Fiecare regulă de comandă trebuie să înceapă cu un caracter tabulator -- acesta condiție cerută! Lipsa unui caracter tabulator la începutul unei linii de regulă este cea mai frecventă greșeală. Din fericire, astfel de erori sunt ușor de detectat, deoarece le raportează.

Exemplu Makefile.

Mai jos este un exemplu simplu (numerele liniilor adăugate pentru claritate).

1 client: conn.o 2g++ client.cpp conn.o -o client 3 conn.o: conn.cpp conn.h 4g++ -c conn.cpp -o conn.o

În acest exemplu, linia care conține textul
client:conn.o ,
se numește „linie de dependență” și linia
g++ client.cpp conn.o -o client
se numește „regulă” și descrie acțiunea care trebuie efectuată.

Și acum mai detaliat despre exemplul dat mai sus:

  • Ținta este setată la fișierul executabil client, care depinde de fișierul obiect conn.o
  • Regula pentru asamblarea unei ținte date
  • A treia linie specifică conn.o țintă și fișierele de care depinde, conn.cpp și conn.h.
  • A patra linie descrie acțiunea de a construi ținta conn.o.

Comentarii

Rândurile care încep cu „#” sunt comentarii

Mai jos este un exemplu de makefile cu comentarii:

1 # Creați un fișier executabil „client” 2 client: conn.o 3g++ client.cpp conn.o -o client 4 5 # Creați fișierul obiect „conn.o” 6 conn.o: conn.cpp conn.h 7g++ -c conn.cpp -o conn.o

Țintă „falsă”.

În mod obișnuit, țintele „fatice”, care reprezintă un nume de fișier țintă „imaginar”, sunt utilizate atunci când apar conflicte între numele țintă și numele fișierelor atunci când numele țintă este specificat în mod explicit pe linia de comandă.

Să presupunem că există o regulă în fișierul make care nu creează nimic, de exemplu:

Curățare: rm *.o temp

Deoarece comanda rm nu creează un fișier numit clean, un astfel de fișier nu va exista niciodată și, prin urmare, comanda make clean va rula întotdeauna.

Cu toate acestea, această regulă nu va funcționa dacă există un fișier numit clean în directorul curent. Deoarece ținta curată nu are dependențe, nu va fi niciodată considerată depreciată și, prin urmare, comanda „rm *.o temp” nu va fi niciodată executată. (atunci când rulați, faceți verificări ale datelor de modificare ale fișierului țintă și ale acelor fișiere de care depinde. Și dacă ținta se dovedește a fi „mai veche”, atunci make execută regulile de comandă corespunzătoare - nota editorului) Pentru eliminare probleme similareși există o declarație specială .PHONY care declară o țintă „falsă”. De exemplu:

FONIC: curat

Astfel, indicăm necesitatea executării scopului, atunci când îl specificăm explicit, sub formă de make clean, indiferent dacă un fișier cu acel nume există sau nu.

Variabile

Puteți defini o variabilă într-un makefile astfel:

$VAR_NAME=valoare

Prin convenție, numele variabilelor sunt scrise cu majuscule:

$OBJECTS=principal.o test.o

Pentru a obține valoarea unei variabile, trebuie să includeți numele acesteia paranteze rotundeși precedați-le cu un simbol „$”, de exemplu:

$(VAR_NAME)

Există două tipuri de variabile în makefiles: "pur și simplu calculat"Și "calculat recursiv".

TOPDIR=/home/tedi/project SRCDIR=$(TOPDIR)/src

Când accesați variabila SRCDIR, veți obține valoarea /home/tedi/project/src .

Cu toate acestea, variabilele recursive pot să nu fie întotdeauna evaluate, cum ar fi următoarele definiții:

CC = gcc -o CC = $(CC) -O2

va avea ca rezultat un ciclu nesfârșit. Pentru a rezolva această problemă, ar trebui să utilizați variabile „calculate simplificate”:

CC:= gcc -o CC += $(CC) -O2

Unde simbolul „:=" creează o variabilă CC și îi atribuie valoarea „gcc -o”. Și simbolul „+=" adaugă „-O2” la valoarea variabilei CC.

Concluzie

Sper că este ghid rapid conține suficiente informații pentru a începe să vă creați fișierele make. Și pentru asta - succes în munca ta.

Bibliografie

  • 1 GNU Make Fișier de documentație, marca de informații.
  • Kurt Wall și colab. Programarea Linux dezlănțuită(Programare pentru Linux în spațiul operațional - nota editorului) , 2001.