Construirea unui program folosind GNU Make. Utilizarea eficientă a GNU Make

make este un utilitar pentru crearea automată a programelor. Vă permite să urmăriți modificările din codul sursă al programului și să compilați nu întregul proiect, ci doar acele fișiere care s-au modificat sau care depind de modificările efectuate. Pentru proiectele mari, acest lucru 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.

Cel mai adesea, construirea unui proiect pe sistemul de operare Linux, ținând cont de dependențe și actualizări, este realizată de utilitar face, care utilizează un script de compilare preformatat pentru aceasta. Am recurs deja în mod repetat la ajutorul acestui utilitar în articolele anterioare, iar acest articol va fi dedicat exclusiv problemelor de utilizare a utilitarului face.

face utilitate

Utilitate face determină automat ce părți ale unui proiect mare s-au schimbat și trebuie să fie recompilate și efectuează acțiunile necesare pentru a face acest lucru. Dar, de fapt, domeniul de aplicare a make nu se limitează doar la crearea de programe, poate fi folosit și pentru a rezolva alte probleme în care unele fișiere ar trebui actualizate automat atunci când alte fișiere sunt modificate.

Utilitate face disponibil pentru diferite sisteme de operare și, datorită caracteristicilor de implementare, împreună cu implementarea „nativă”, multe sisteme de operare au o implementare GNU gmake, iar comportamentul acestor implementări în unele sisteme de operare, de exemplu, Solaris, poate diferi semnificativ. Prin urmare, se recomandă să specificați numele unui anumit utilitar în scripturile de compilare. În sistemul de operare Linux, aceste două nume sunt sinonime, implementate printr-o legătură simbolică, după cum se arată mai jos:

$ ls -l /usr/bin/*make lrwxrwxrwx 1 root root 4 Oct 28 2008 /usr/bin/gmake -> make -rwxr-xr-x 1 root root 162652 25 mai 2008 /usr/bin/make ... $ make --version GNU Make 3.81 ...

În mod implicit, numele fișierului script de compilare este Makefile. Utilitate face asigură asamblarea completă a celor specificate obiective prezente în scenariu, de exemplu:

$ make $ make clean

Dacă ținta nu este specificată în mod explicit, atunci primul secvenţialțintă în fișierul script. De asemenea, puteți specifica orice alt fișier script care va fi utilizat pentru asamblare:

$ make -f Makefile.my

Cel mai simplu dosar Makefile constă din construcţii sintactice de două tipuri: scopuri şi macrodefiniţii. Descrierea țintei constă din trei părți: numele țintei, o listă de dependențe și o listă de comenzi shell necesare pentru a construi ținta. Numele țintă este o listă nevide de fișiere care ar trebui să fie create. Lista de dependențe - o listă de fișiere în funcție de care este construită ținta. Numele țintă și lista de dependențe alcătuiesc antetul țintă, scrise pe o singură linie și separate prin două puncte (":"). Lista comenzilor este scrisă din linia următoare, cu toate comenzile începând cu caracterul tabulator necesar. Multe editoare de text pot fi configurate pentru a înlocui caracterele de tabulatură cu spații. Acest fapt merită luat în considerare și verificat editorul în care editați Makefile, nu înlocuiește filele cu spații, deoarece această problemă apare destul de des. Orice linie din secvența listei de comenzi care nu începe cu o filă (o altă comandă) sau " # „ (comentar) – este considerată finalizarea obiectivului actual și începutul unuia nou.

Utilitate face are mulți parametri interni cu valori implicite, dintre care cele mai importante sunt regulile de procesare a sufixelor, precum și definițiile variabilelor de mediu interne. Aceste date se numesc o bază de date face si poate fi vizualizat astfel:

$ make -p >make.suffix make: *** Nicio țintă specificată și niciun makefile găsit. Stop. $ cat make.sufx # GNU Make 3.81 # Copyright (C) 2006 Free Software Foundation, Inc. ... # Database Make, printed Thu Apr 14 14:48:51 2011 ... CC = cc LD = ld AR = ar CXX = g++ COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $ ( TARGET_ARCH) -c COMPILE.C = $(COMPILE.cc) ... SUFIXE:= .out .a .ln .o .c .cc .C .cpp .p .f .F .r .y .l .s .S .mod .sym \ .def .h .info .dvi .tex .texinfo .texi .txiinfo .w .ch... # Reguli implicite ... %.o: %.c # comenzi de executat (construit -in): $(COMPILE.c) $(OUTPUT_OPTION) $< ...

Valorile tuturor acestor variabile sunt: CC, LD, AR, EXTRA_CFLAGS, ... poate fi folosit de fișierul script ca definiții implicite cu valori implicite. În plus, vă puteți defini pe propriul dvs reguli procesare implicită pentru selectate sufixe(extensii de nume de fișier), așa cum se arată în exemplul de mai sus pentru fișierele sursă de cod C: %.c.

Cele mai multe medii de dezvoltare integrate (IDE) sau pachete pentru crearea de instalații portabile (de exemplu, automake sau autoconf) au sarcina de a crea un fișier Makefile pentru utilitate face.

Cum să accelerezi construcția

Asamblarea proiectelor simple are loc destul de repede, dar ținând cont de creșterea proiectului pe măsură ce se dezvoltă, timpul de asamblare, a cărui parte principală este cheltuită pentru compilare, poate crește semnificativ. Un exemplu binecunoscut de acest fel este construirea nucleului Linux, care, în funcție de tipul de hardware, poate dura de la zeci de minute la ore de timp CPU. Situația este agravată de faptul că atunci când lucrați la un proiect (modificare de cod, depanare, găsire de erori, testare etc.) poate fi necesar să reconstruiți proiectul de câteva zeci de ori pe zi. Prin urmare, oportunitățile de a accelera acest proces devin cu adevărat relevante.

Deoarece astăzi sistemele cu un singur procesor (single-core) au fost aproape înlocuite cu configurații multi-core, asamblarea multor proiecte poate fi accelerată semnificativ (de multe ori) folosind oportunitatea face rulați mai multe lucrări de construcție în paralel folosind cheia –j ca mai jos:

$ man make ... -j , --jobs[=jobs] Specifică numărul de joburi (comenzi) de rulat simultan. ...

Să verificăm beneficiile oferite de această caracteristică folosind un exemplu practic. Ca standard pentru asamblare, să luăm un proiect de server NTP, care nu durează foarte mult pentru a asambla, dar nici prea repede:

$ pwd /usr/src/ntp-4.2.6p3

Mai întâi, să rulăm construirea pe un procesor Atom cu 4 nuclee (nu un model foarte rapid cu o frecvență de 1,66 Ghz), dar cu un SSD foarte rapid:

$ cat /proc/cpuinfo | head -n10 procesor: 0 vendor_id: GenuineIntel cpu family: 6 model: 28 model name: Intel(R) Atom(TM) CPU 330 @ 1.60GHz stepping: 2 CPU MHz: 1596.331 cache size: 512 KB $ make clean # start the construiți în patru fire $ time make -j4 ... real 1m5.023s utilizator 2m40.270s sys 0m16.809s $ make clean # rulați construirea în modul standard fără paralelism $ time make ... real 2m6.534s utilizator 1m56.119s sys 0m12 .193s $ make clean # rulează build-ul cu nivelul de paralelism selectat automat $ time make -j ... real 1m5.708s user 2m43.230s sys 0m16.301s

După cum puteți vedea, utilizarea paralelismului (explicit sau implicit) vă permite să accelerați asamblarea de aproape două ori - 1 minut față de 2. Să construim același proiect pe un procesor cu 2 nuclee mai rapid, dar cu un HDD obișnuit destul de lent:

$ cat /proc/cpuinfo | head -n10 procesor: 0 vendor_id: GenuineIntel cpu family: 6 model: 23 model name: Pentium(R) Dual-Core CPU E6600 @ 3.06GHz stepping: 10 CPU MHz: 3066.000 cache size: 2048 KB ... $ time make . .. real 0m31.591s utilizator 0m21.794s sys 0m4.303s $ time make -j2 ... real 0m23.629s utilizator 0m21.013s sys 0m3.278s

Deși viteza finală de construire a crescut de 3-4 ori, îmbunătățirea numărului de procesoare este de numai 20%, deoarece „veriga slabă” aici este unitatea lentă, care permite o întârziere la scrierea unui număr mare de mici fișiere de proiect .obj.

Notă: Aș dori să vă reamintesc că nu orice adunare face, care rulează cu succes pe un procesor (cum este cazul implicit sau când se specifică -j1), va rula cu succes și cu un număr mai mare de procesoare implicate. Acest lucru se datorează perturbărilor în sincronizarea operațiunilor în cazul ansamblurilor complexe. Cel mai evident exemplu al unei astfel de build care eșuează în cazul execuției paralele este construirea nucleului Linux pentru unele versiuni ale nucleului. Posibilitate de executie paralela face trebuie verificat experimental pentru proiectul care este asamblat. Dar, în majoritatea cazurilor, această caracteristică poate fi folosită și vă permite să accelerați semnificativ procesul de asamblare!

Dacă această metodă de accelerare a procesului de asamblare se bazează pe faptul că acum marea majoritate a sistemelor sunt multiprocesoare (multi-core), atunci următoarea metodă profită de faptul că cantitatea de memorie RAM de pe computerele moderne (2- 4-8 GB) depășește semnificativ cantitatea de memorie necesară pentru codul programului de compilare. În acest caz, compilarea, principalul factor limitator pentru care este crearea multor fișiere obiect, poate fi mutată în zona unui disc special creat (disc RAM, tmpfs), aflat în memorie:

$ total folosit gratuit buffere partajate memorate în cache Mem: 4124164 1516980 2607184 0 248060 715964 -/+ buffere/cache: 552956 3571208 Schimbare: 4606972 0 46 $ df - 971208 | grep tmp tmpfs 2014 1 2014 1% /dev/shm

Acum puteți transfera temporar fișierele proiectului asamblat în tmpfs(folosim în continuare serverul NTP din exemplul anterior), în director /dev/shm:

$ pwd /dev/shm/ntp-4.2.6p3 $ make -j ... real 0m4.081s utilizator 0m1.710s sys 0m1.149s

În acest caz, ambele metode de îmbunătățire a performanței sunt utilizate simultan, iar îmbunătățirea față de compilația originală este aproape de un ordin de mărime. Adevărat, acest exemplu a fost rulat pe un sistem cu un HDD lent, unde asamblarea paralelă nu a oferit practic niciun câștig și a necesitat aproximativ 30 de secunde.

Această metodă de accelerare poate fi aplicată la construirea nucleului Linux, pentru care, așa cum sa menționat deja, construirea paralelă nu funcționează. Pentru a profita de memoria RAM, copiați arborele sursă a nucleului în director /dev/shm:

$ pwd /dev/shm/linux-2.6.35.i686 $ time make bzImage ... HOSTCC arch/x86/boot/tools/build BUILD arch/x86/boot/bzImage Dispozitivul rădăcină este (8, 1) Configurarea este 13052 octeți (completat la 13312 octeți). Sistemul este 3604 kB CRC 418921f4 Kernel: arch/x86/boot/bzImaginea este gata (nr. 1) real 9m23.986s utilizator 7m4.826s sys 1m18.529s

După cum puteți vedea, construirea nucleului Linux a durat mai puțin de 10 minute, ceea ce este un rezultat neobișnuit de bun.

În concluzie, vă putem sfătui să optimizați cu atenție condițiile de asamblare a proiectului pentru echipamentele folosite pentru aceasta și, având în vedere că în timpul procesului de depanare montajul se efectuează de sute de ori, puteți economisi mult timp!

Construirea modulelor nucleului

Un caz special de construire de aplicații este construirea modulelor kernel-ului Linux (drivere). Pornind de la versiunile de kernel 2.6, pentru a construi un modul, Makefile, construit pe utilizarea macrocomenzilor, și tot ce trebuie să facem este să scriem (pentru un fișier cu propriul nostru cod numit mod_params.c), următorul șablon pentru asamblarea modulelor:

Listare 1. Makefile pentru construirea modulelor kernelului
CURRENT = $(shell uname -r) KDIR = /lib/modules/$(CURRENT)/build PWD = $(shell pwd) TARGET = mod_params obj-m:= $(TARGET).o implicit: $(MAKE) - C $(KDIR) M=$(PWD) module...$ make make -C /lib/modules/2.6.18-92.el5/build \ M=examples/modules-done_1/hello_printk modules make: Introducerea directorului `/usr/src/kernels/2.6.18-92.el5- i686" CC [M] /examples/modules-done_1/hello_printk/hello_printk.o Construirea modulelor, etapa 2. MODPOST CC /examples/modules-done_1/hello_printk/hello_printk.mod.o LD [M] examples/modules-done_1/ hello_printk/hello_printk.ko make: Leaving directory `/usr/src/kernels/2.6.18-92.el5-i686" $ ls -l *.o *.ko -rw-rw-r-- 1 olej olej 74391 Mar 19 15:58 hello_printk.ko -rw-rw-r-- 1 olej olej 42180 Mar 19 15:58 hello_printk.mod.o -rw-rw-r-- 1 olej olej 33388 Mar 19 15:58 $ hello_print fișier hello_printk.ko hello_printk.ko: ELF pe 32 de biți LSB relocabil, Intel 80386, versiunea 1 (SYSV), nedemontat $ /sbin/modinfo hello_printk.ko nume fișier: hello_printk.ko autor: Oleg Tsiliuric licență: GPL srcversion: 83915F228EC39FFCBAF99FD depinde: vermagic: 2.6.18-92.el5 SMP mod_unload 686 REGPARM 4KSTACKS gcc-4.1

Concluzie

Articolul a discutat aspecte ale lucrului cu utilitatea make, care nu sunt descrise adesea în literatură, dar pot fi extrem de utile în munca practică. De asemenea, am încheiat o discuție despre problemele legate de livrarea și asamblarea software-ului pe sistemul de operare Linux.

În următorul articol vom începe introducerea noastră în bibliotecile API prezente în sistemele POSIX.

Scrierea unui makefile poate deveni uneori o durere de cap. Cu toate acestea, dacă vă dați seama, totul se încadrează la locul său, iar scrierea unui makefile puternic de 40 de linii pentru orice proiect mare se poate face rapid și elegant.

Atenţie! Se presupun cunoștințe de bază despre utilitarul GNU make.

Avem un proiect abstract tipic cu următoarea structură de directoare:

Să folosim ceva de genul #include pentru a include fișiere de antet în surse , adică directorul proiect/include este făcut standard în timpul compilării.

După asamblare ar trebui să arate așa:

Adică directorul bin conține versiunile de lucru (aplicație) și de depanare (application_debug), în subdirectoarele Release și Debug ale directorului proiect/obj structura directorului proiect/src se repetă cu codurile sursă corespunzătoare ale fișierelor obiect. , din care este compilat conținutul directorului bin.

Pentru a obține acest efect, creați un Makefile în directorul proiectului cu următorul conținut:

  1. root_include_dir:= include
  2. root_source_dir:=src
  3. surse_subdirs:= . dir1 dir2
  4. compile_flags:= -Wall -MD -pipe
  5. link_flags:= -s -pipe
  6. biblioteci:= -ldl
  7. relative_include_dirs:= $(addprefix ../ ../ , $(root_include_dir) )
  8. relative_source_dirs:= $(addprefix ../ ../ $(root_source_dir) / , $(source_subdirs) )
  9. objects_dirs:= $(addprefix $(root_source_dir) / , $(source_subdirs) )
  10. obiecte:= $(patsubst ../ ../% , % , $(wildcard $(addsufx /* .c* , $(relative_source_dirs) ) ) )
  11. obiecte:= $(obiecte:.cpp=.o)
  12. obiecte:= $(obiecte:.c=.o)
  13. toate: $(nume_program)
  14. $(nume_program): obj_dirs $(obiecte)
  15. g++ -o $@ $(obiecte) $(link_flags) $(biblioteci)
  16. obj_dirs:
  17. mkdir -p $(directoare_obiecte)
  18. VPATH:= ../ ../
  19. %.o: %.cpp
  20. g++ -o $@ -c $< $(compile_flags) $(build_flags) $(addprefix -I, $(relative_include_dirs) )
  21. %.o: %.c
  22. g++ -o $@ -c $< $(compile_flags) $(build_flags) $(addprefix -I, $(relative_include_dirs) )
  23. .FONICE: curat
  24. curat:
  25. rm -rf bin obj
  26. include $(caracterul metalic $(addsufix /* .d, $(objects_dirs) ) )

În forma sa pură, un astfel de makefile este util doar pentru atingerea obiectivului curat, care va elimina directoarele bin și obj.
Să adăugăm un alt script numit Release pentru a construi versiunea de lucru:

Mkdir -p bin mkdir -p obj mkdir -p obj/Release make --directory=./obj/Release --makefile=../../Makefile build_flags="-O2 -fomit-frame-pointer" program_name=. ./../bin/application

Și un alt script de depanare pentru a construi versiunea de depanare:

Mkdir -p bin mkdir -p obj mkdir -p obj/Debug make --directory=./obj/Debug --makefile=../../Makefile build_flags="-O0 -g3 -D_DEBUG" program_name=../ ../bin/application_debug

Apelarea unuia dintre ele va asambla proiectul nostru într-o versiune de lucru sau de depanare. Și acum, pe primul loc.

Să presupunem că trebuie să construim o versiune de depanare. Accesați directorul proiectului și apelați ./Debug. Primele trei linii creează directoarele. În a patra linie, utilitarul make este informat că directorul curent la pornire ar trebui să fie project/obj/Debug, în legătură cu acesta, se trece apoi calea către makefile și sunt setate două constante: build_flags (steaguri de compilare importante pentru versiunea de depanare sunt listate aici) și program_name (pentru versiunea de depanare - aceasta este application_debug).

1: O variabilă este declarată cu numele directorului rădăcină al fișierelor de antet.

2: O variabilă este declarată cu numele directorului rădăcină sursă.

3: Este declarată o variabilă cu numele subdirectoarelor din directorul sursă rădăcină.

4: Este declarată o variabilă cu steaguri de compilare generale. -MD forțează compilatorul să genereze un fișier de dependență cu același nume cu extensia .d pentru fiecare sursă. Fiecare astfel de fișier arată ca o regulă, unde ținta este numele sursei, iar dependențele sunt toate fișierele sursă și antet pe care le include cu directiva #include. Indicatorul -pipe forțează compilatorul să folosească IPC în loc de sistemul de fișiere, ceea ce accelerează oarecum compilarea.

5: Este declarată o variabilă cu steaguri de aspect general. -s obligă linkerul să elimine secțiunile .symtab, .strtab și o grămadă de alte secțiuni cu nume precum .debug* din fișierul ELF rezultat, ceea ce îi reduce semnificativ dimensiunea. Pentru o mai bună depanare, această cheie poate fi eliminată.

6: O variabilă este declarată cu numele bibliotecilor utilizate ca chei de legătură.

8: Este declarată o variabilă care conține numele relative ale directoarelor cu fișiere de antet standard. Apoi astfel de nume sunt transmise direct compilatorului, precedate de comutatorul -I. Pentru cazul nostru, va fi ../../include, deoarece avem un singur astfel de director. Funcția addprefix adaugă primul argument la toate obiectivele specificate de al doilea argument.

9: Este declarată o variabilă care conține numele relative ale tuturor subdirectoarelor din directorul sursă rădăcină. Ca rezultat, obținem: ../../src/. ../../src/dir1 ../../src/dir1.

10: Este declarată o variabilă care conține numele subdirectoarelor directorului project/obj/Debug/src în raport cu proiectul/obj/Debug curent. Adică, cu aceasta listăm o copie a structurii directorului proiect/src. Ca rezultat, obținem: /src/dir1 src/dir2.

11: Este declarată o variabilă care conține numele surselor găsite pe baza fișierelor *.c* cu același nume (.cpp\.c), indiferent de directorul curent. Să ne uităm la asta pas cu pas: rezultatul sufixului va fi ../../src/./*.с* ../../src/dir1/*.с* ../../src/ dir2/*.с*. Funcția wildcard va extinde modelele cu asteriscuri la nume reale de fișiere: ../../src/./main.сpp ../../src/dir1/file1.с../../src/dir1/file2 .сpp ../../src/dir2/file3.с../../src/dir2/file4.с. Funcția patsubsb va elimina prefixul ../../ din numele fișierelor (înlocuiește modelul dat în primul argument cu modelul din al doilea argument și % reprezintă orice număr de caractere). Ca rezultat, obținem: src/./main.сpp src/dir1/file1.с src/dir1/file2.сpp src/dir2/file3.с src/dir2/file4.с.

12: În variabila cu numele surselor de extensie, .cpp este înlocuit cu .o.

13: În variabila cu numele surselor de extensie, .c se înlocuiește cu .o.

15: Prima regulă anunțată - scopul ei devine scopul întregului proiect. Dependența este o constantă care conține numele programului (../../bin/application_debug am trecut-o când am rulat make din script).

17: Descrierea obiectivului cheie. De asemenea, dependențele sunt evidente: prezența subdirectoarelor create în proiect/obj/Debug, repetând structura directorului proiect/src și multe fișiere obiect în ele.

18: Este descrisă acțiunea de a lega fișierele obiect într-o țintă.

20: Regulă în care ținta este directorul proiect/obj/Debug/src și subdirectoarele acestuia.

21: Acțiunea pentru atingerea obiectivului este de a crea directoarele adecvate src/., src/dir1 și src/dir2. Comutatorul -p al utilitarului mkdir ignoră eroarea dacă, la crearea unui director, acesta există deja.

23: Variabila VPATH ia valoarea ../../. Acest lucru este necesar pentru următoarele șabloane de reguli.

25: Descrie un set de reguli ale căror ținte sunt orice ținte care se potrivesc cu modelul %.o (adică ale căror nume se termină în .o) și ale căror dependențe sunt ținte cu același nume care se potrivesc cu modelul %.cpp (adică , ale căror nume se termină în .cpp). În acest caz, același nume este înțeles nu numai ca o potrivire exactă, ci și dacă numele dependenței este precedat de conținutul variabilei VPATH. De exemplu, numele src/dir1/file2 și ../../src/dir1/file2 se vor potrivi deoarece VPATH conține ../../.

26: Apelarea compilatorului pentru a transforma sursa C++ într-un fișier obiect.

28: Descrie un set de reguli ale căror ținte sunt orice ținte care se potrivesc cu modelul %.o (adică ale căror nume se termină în .o) și ale căror dependențe sunt ținte cu același nume care se potrivesc cu modelul %.c (adică , ale căror nume se termină în .c). Același nume ca în rândul 23.

29: Apelarea compilatorului pentru a transforma sursa C într-un fișier obiect.

31: Un gol curățat este declarat abstract. Atingerea unui scop abstract are loc întotdeauna și nu depinde de existența unui fișier cu același nume.

32: Declararea obiectivului abstract curat.

33: Acțiunea pentru a realiza acest lucru este de a distruge directoarele proiect/bin și proiect/obj cu tot conținutul lor.

36: Include conținutul tuturor fișierelor de dependență (cu extensia .d) situate în subdirectoarele directorului curent. Utilitarul make efectuează această acțiune la începutul analizei fișierului make. Cu toate acestea, fișierele de dependență sunt create numai după compilare. Aceasta înseamnă că în timpul primei versiuni, nu va fi inclus niciun astfel de fișier. Dar nu este înfricoșător. Scopul includerii acestor fișiere este de a forța o recompilare a surselor care depind de fișierul antet modificat. Pe a doua versiune și pe cele ulterioare, make va include regulile descrise în toate fișierele de dependență și, dacă este necesar, va atinge toate obiectivele care depind de fișierul antet modificat.

La un moment dat, mi-a lipsit foarte mult un astfel de manual pentru înțelegerea lucrurilor de bază despre make-ul. Cred că va fi interesant măcar pentru cineva. Deși această tehnologie este pe cale de dispariție, este încă folosită în multe proiecte. Nu a existat suficientă karmă pentru hub-ul „Traduceri”, de îndată ce va apărea ocazia, o voi adăuga și acolo. Adăugat la Traduceri. Dacă există erori în design, vă rugăm să le semnalați. Îl voi repara.

Articolul va fi de interes în primul rând pentru cei care studiază programarea C/C++ pe sisteme asemănătoare UNIX de la rădăcini, fără a utiliza un IDE.

Compilarea manuală a unui proiect este o sarcină foarte obositoare, mai ales când există mai multe fișiere sursă, iar pentru fiecare dintre ele trebuie să introduceți de fiecare dată comenzi de compilare și de legătură. Dar nu este chiar atât de rău. Acum vom învăța cum să creăm și să folosim Makefiles. Un Makefile este un set de instrucțiuni pentru programul make, care vă ajută să construiți un proiect software cu o singură atingere.

Pentru a exersa, va trebui să creați un proiect microscopic la Hello World din patru fișiere într-un singur director:

principal.cpp

#include #include „functions.h” folosind namespace std; int main())( print_hello(); cout<< endl; cout << "The factorial of 5 is " << factorial(5) << endl; return 0; }


salut.cpp

#include #include „functions.h” folosind namespace std; void print_hello())( cout<< "Hello World!"; }


factorial.cpp

#include "functions.h" int factorial(int n)( if(n!=1)( return(n * factorial(n-1)); ) else return 1; )


funcţii.h

void print_hello(); int factorial(int n);


Puteți descărca totul în vrac de aici
Autorul a folosit limbajul C++, care nu este deloc necesar să-l cunoaștem, și compilatorul g++ de la gcc. Cel mai probabil, orice alt compilator va funcționa și el. Fișierele sunt ușor corectate, astfel încât să fie compilate cu gcc 4.7.1
face program
Dacă fugi
face
apoi programul va încerca să găsească un fișier cu numele implicit Makefile în directorul curent și să execute instrucțiuni din acesta. Dacă în directorul curent există mai multe fișiere makefile, îl puteți indica pe cel de care aveți nevoie astfel:
make -f MyMakefile
Există mulți alți parametri de care nu avem nevoie încă. Puteți afla despre ele în pagina de manual.
Procesul de construire
Compilatorul preia fișierele codului sursă și produce fișiere obiect din ele. Linkerul preia apoi fișierele obiect și produce un executabil din ele. Asamblare = compilare + legare.
Compilare manuală
Cel mai simplu mod de a construi programul:
g++ main.cpp hello.cpp factorial.cpp -o salut
Este incomod să tastați acest lucru de fiecare dată, așa că îl vom automatiza.
Cel mai simplu Makefile
Ar trebui să conțină următoarele părți:
tinta: echipa dependentelor
Pentru exemplul nostru, makefile-ul va arăta astfel:
toate: g++ main.cpp hello.cpp factorial.cpp -o salut
Vă rugăm să rețineți că linia de comandă trebuie să înceapă cu o filă! Salvați-l ca Makefile-1 în directorul proiectului și rulați build-ul cu make -f Makefile-1
În primul exemplu ținta este numită all . Aceasta este ținta implicită pentru makefile și va fi executată dacă nicio altă țintă nu este specificată în mod explicit. De asemenea, această țintă din acest exemplu nu are dependențe, așa că make începe imediat executarea comenzii dorite. Și comanda, la rândul său, lansează compilatorul.
Utilizarea dependențelor
Utilizarea mai multor ținte într-un singur makefile este utilă pentru proiecte mari. Acest lucru se datorează faptului că, dacă schimbați un fișier, nu va trebui să reconstruiți întregul proiect, dar vă puteți descurca cu reconstruirea doar a părții modificate. Exemplu:
all: salut salut: main.o factorial.o hello.o g++ main.o factorial.o hello.o -o salut main.o: main.cpp g++ -c main.cpp factorial.o: factorial.cpp g++ -c factorial.cpp hello.o: hello.cpp g++ -c hello.cpp clean: rm -rf *.o salut
Aceasta ar trebui salvată sub numele Makefile-2, toate în același director

Acum ținta all are doar o dependență, dar nicio comandă. În acest caz, atunci când este apelat, make va executa secvenţial toate dependenţele specificate în fişier pentru această ţintă.
A fost adăugat și un nou obiectiv, curat. Este folosit în mod tradițional pentru a curăța rapid toate rezultatele construcției unui proiect. Curățarea începe astfel: make -f Makefile-2 clean

Utilizarea variabilelor și comentariilor
Variabilele sunt utilizate pe scară largă în fișierele make. De exemplu, aceasta este o modalitate convenabilă de a lua în considerare posibilitatea ca proiectul să fie compilat cu un compilator diferit sau cu opțiuni diferite.
# Acesta este un comentariu care spune că variabila CC specifică compilatorul folosit pentru a construi CC=g++ # Acesta este un alt comentariu. El explică că variabila CFLAGS conține steaguri care sunt transmise compilatorului CFLAGS=-c -Wall all: hello hello: main.o factorial.o hello.o $(CC) main.o factorial.o hello.o -o hello main .o: main.cpp $(CC) $(CFLAGS) main.cpp factorial.o: factorial.cpp $(CC) $(CFLAGS) factorial.cpp hello.o: hello.cpp $(CC) $(CFLAGS) ) salut.cpp curat: rm -rf *.o salut
Acesta este Makefile-3
Variabilele sunt un lucru foarte convenabil. Pentru a le folosi, pur și simplu le atribuiți o valoare înainte de a le folosi. După aceea, puteți înlocui valoarea lor în locul potrivit în acest mod: $(VAR)
Ce e de facut in continuare
După această scurtă instrucțiune, puteți încerca deja să creați singur makefiles simple. În continuare, trebuie să citiți manuale și manuale serioase. Ca acord final, puteți încerca să îl dezasamblați singur și să realizați un astfel de makefile universal, care poate fi adaptat la aproape orice proiect în două atingeri:
CC=g++ CFLAGS=-c -Wall LDFLAGS= SOURCES=main.cpp hello.cpp factorial.cpp OBJECTS=$(SOURCES:.cpp=.o) EXECUTABLE=salut toti: $(SOURCES) $(EXECUTABLE) $(EXECUTABLE) ): $(OBIECTE) $(CC) $(LDFLAGS) $(OBIECTE) -o $@ .cpp.o: $(CC) $(CFLAGS) $< -o $@
Makefile-4
Noroc!

Utilitarul determină automat ce părți ale unui program mare trebuie să fie recompilate și comenzile pentru a le recompila. 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, un fișier executabil depinde de fișierele obiect, care la rândul lor depind de fișierele sursă și antet. Pentru nume makefile titlu recomandat GNUmakefile, makefile sau Makefile, iar căutarea are loc exact în ordinea specificată. 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. Liniile lungi sunt împărțite în mai multe linii folosind o bară oblică inversă urmată de o linie nouă. 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 " . "). Aceasta se numește ținta principală 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 după propria sa regulă. Recopilarea trebuie efectuată dacă fișierul sursă sau oricare dintre fișierele antet menționate printre dependențe este actualizat mai târziu decât fișierul obiect, sau dacă fișierul obiect nu există.
Regulă curat nu corespunde niciunui fișier creat și, în consecință, 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ă omiteți această regulă, atunci 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 pe linia de comandă 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