Una breu introducció als Makefiles en el desenvolupament de programari de codi obert amb GNU Make


GNU Make és una utilitat de desenvolupament que determina les parts d'una base de codi particular que s'han de recompilar i pot emetre ordres per dur a terme aquestes operacions a la base de codi. Aquesta utilitat particular make es pot utilitzar amb qualsevol llenguatge de programació sempre que la seva compilació es pugui fer des de l'intèrpret d'ordres emetent ordres.

Per utilitzar GNU Make, hem de tenir un conjunt de regles que defineixin la relació entre diferents fitxers del nostre programa i ordres per actualitzar cada fitxer. S'escriuen en un fitxer especial anomenat makefile. L'ordre 'make' utilitza la base de dades 'makefile' i els temps de darrera modificació dels fitxers per decidir quins fitxers s'han de tornar a compilar.

Contingut d'un Makefile

En general, els makefiles contenen 5 tipus de coses, a saber: regles implícites, regles explícites, definicions de variables, directives i comentaris.

  1. Una regla explícita especifica com crear/reconstruir un o més fitxers (anomenats objectius, s'explicarà més endavant) i quan fer-ho.
  2. Una regla implícita especifica com crear/reformar un o més fitxers en funció dels seus noms. Descriu com es relaciona un nom de fitxer de destinació amb un fitxer amb un nom semblant a l'objectiu.
  3. Una definició de variable és una línia que especifica un valor de cadena per a una variable que es substituirà més endavant.
  4. Una directiva és una instrucció perquè make faci alguna cosa especial mentre llegeix el fitxer makefile.
  5. S'utilitza un símbol # per representar l'inici d'un comentari dins dels makefiles. Simplement s'ignora una línia que comença amb #.

La informació que indica a make com recompilar un sistema prové de la lectura d'una base de dades anomenada makefile. Un simple makefile constarà de regles de la sintaxi següent:

target ... : prerequisites ... 
	recipe 
... 
...

Un destí es defineix com el fitxer de sortida generat pel programa. També poden ser objectius falsos, que s'explicaran a continuació. Alguns exemples de fitxers de destinació inclouen executables, fitxers objecte o objectius falsos com ara netejar, instal·lar, desinstal·lar, etc.

Un requisit previ és un fitxer que s'utilitza com a entrada per crear els fitxers de destinació.

Una recepta és l'acció que fa realitzar per crear el fitxer de destinació en funció dels requisits previs. Cal posar el caràcter de tabulació abans de cada recepta dins dels makefiles tret que especifiquem la variable '.RECIPEPREFIX' per definir algun altre caràcter com a prefix de la recepta.

final: main.o end.o inter.o start.o
	gcc -o final main.o end.o inter.o start.o
main.o: main.c global.h
	gcc -c main.c
end.o: end.c local.h global.h
	gcc -c end.c
inter.o: inter.c global.h
	gcc -c inter.c
start.o: start.c global.h
	gcc -c start.c
clean:
	rm -f main.o end.o inter.o start.o

A l'exemple anterior hem utilitzat 4 fitxers font C i dos fitxers de capçalera per crear l'executable final. Aquí cada fitxer ‘.o’ és alhora un objectiu i un requisit previ dins del makefile. Ara feu una ullada a l'últim nom de destinació clean. És només una acció en lloc d'un fitxer objectiu.

Com que normalment no ho necessitem durant la compilació, no està escrit com a requisit previ en cap altra regla. Els objectius que no fan referència a fitxers sinó que són només accions s'anomenen objectius falsos. No tindran cap requisit previ com altres fitxers de destinació.

Per defecte, make comença amb el primer objectiu al 'makefile' i s'anomena 'objectiu predeterminat'. Tenint en compte el nostre exemple, tenim final com a primer objectiu. Com que els seus requisits previs inclouen altres fitxers d'objecte, aquests s'han d'actualitzar abans de crear el final. Cadascun d'aquests requisits previs es processa segons les seves pròpies regles.

La recompilació es produeix si es fan modificacions als fitxers font o als fitxers de capçalera o si el fitxer objecte no existeix. Després de recompilar els fitxers d'objectes necessaris, make decideix si torna a enllaçar final o no. Això s'ha de fer si el fitxer final no existeix, o si algun dels fitxers objecte és més nou que ell.

Així, si canviem el fitxer inter.c, en executar make, es recompilarà el fitxer font per actualitzar el fitxer objecte inter.o i després enllaça final.

En el nostre exemple, vam haver d'enumerar tots els fitxers d'objecte dues vegades a la regla per a final tal com es mostra a continuació.

final: main.o end.o inter.o start.o
	gcc -o final main.o end.o inter.o start.o

Per evitar aquestes duplicacions, podem introduir variables per emmagatzemar la llista de fitxers objecte que s'estan utilitzant dins del makefile. Mitjançant l'ús de la variable OBJ podem reescriure el makefile de mostra a un de semblant que es mostra a continuació.

OBJ = main.o end.o inter.o start.o
final: $(OBJ)
	gcc -o final $(OBJ)
main.o: main.c global.h
	gcc -c main.c
end.o: end.c local.h global.h
	gcc -c end.c
inter.o: inter.c global.h
	gcc -c inter.c
start.o: start.c global.h
	gcc -c start.c
clean:
	rm -f $(OBJ)

Com hem vist a l'exemple makefile, podem definir regles per netejar el directori font eliminant els fitxers d'objectes no desitjats després de la compilació. Suposem que tenim un fitxer de destinació anomenat clean. Com es pot fer diferenciar les dues situacions anteriors? Aquí ve el concepte d'objectius falsos.

Un objectiu fals és aquell que no és realment el nom d'un fitxer, sinó que és només un nom per a una recepta que s'executa sempre que es fa una sol·licitud explícita des del makefile. Un dels motius principals per utilitzar un objectiu fals és evitar un conflicte amb un fitxer del mateix nom. Un altre motiu és millorar el rendiment.

Per explicar això, revelaré un gir inesperat. La recepta per a netejar no s'executarà per defecte en executar make. En comptes d'això, cal invocar el mateix emetent l'ordre neteja.

.PHONY: clean
clean:
	rm -f $(OBJ)

Ara proveu de crear makefiles per al vostre propi codi base. No dubteu a comentar aquí amb els vostres dubtes.