Salta al contingut
Llegat fiber

Makefile

Escrius un cop com es construeix el programa i make el compila per tu, refent només el que ha canviat. Regles, dependències, variables, all i clean.

Conceptes clau

  • Regla: <objectiu>: <dependències> i la comanda amb un tabulador
  • make compara dates i només refà el que cal
  • Cada .o depèn del seu .cc i de tots els .hh que inclou
  • Variables: CXX i OPCIONS
  • all, clean i .PHONY

Per què?

Al capítol anterior hem vist que un programa de diversos fitxers es compila en dos passos: cada .cc dona un .o, i el linker els ajunta.
Si canvies un sol fitxer, n’hi ha prou amb refer el seu .o i tornar a enllaçar.

Fer-ho a mà és molt feixuc: has de recordar quins fitxers has tocat, quins .o en depenen i escriure cada vegada la comanda sencera, amb totes les opcions.

Un Makefile és un fitxer de text, a la carpeta del programa, que diu què s’ha de construir, a partir de què i amb quina comanda.

Les regles

Un Makefile està format per una llista de regles.
Cada regla té aquesta forma:

<objectiu>: <dependències>
	<comanda>
Part Què és
<objectiu> El fitxer que es vol fer, normalment un .o o l’executable.
<dependències> Els fitxers que calen per fer-lo, separats per espais.
<comanda> El que s’executa al terminal per fer-lo. Va a la línia de sota.

Alerta: la línia de la comanda ha de començar amb un tabulador, no amb espais.

Un programa amb només main.cc

El Makefile més senzill té dues regles, una per a cada pas del capítol anterior:

# Per fer main.exe cal main.o, i s'aconsegueix enllaçant
main.exe: main.o
	g++ -o main.exe main.o

# Per fer main.o cal main.cc, i s'aconsegueix compilant (-c)
main.o: main.cc
	g++ -c main.cc

Les línies que comencen amb # són comentaris: make se les salta.

enllaçarcompilarmain.exeg++ -o main.exe main.omain.og++ -c main.ccmain.cc
Cada regla és una fletxa: l'objectiu, a dalt, es fa a partir de la dependència, a sota, amb la comanda de la fletxa.

Quan escrius make, passa això:

  1. make agafa la primera regla: vol fer main.exe.
  2. Per fer main.exe necessita main.o, que també té regla. Abans de res, va a fer main.o.
  3. main.o necessita main.cc, que ja hi és. Compila: g++ -c main.cc.
  4. Ara que ja té main.o, torna a main.exe i enllaça: g++ -o main.exe main.o.

make escriu cada comanda al terminal abans d’executar-la:

g++ -c main.cc
g++ -o main.exe main.o

L’ordre de les regles al fitxer no importa, llevat de la primera: és la que make fa quan no li dius res.

Un programa amb una classe

Ara hi afegim una classe, amb classe.hh i classe.cc.
Tant main.cc com classe.cc fan #include "classe.hh", així que classe.hh ha de sortir a les dependències de tots dos .o.

enllaçarcompilarmain.exemain.oclasse.omain.ccclasse.hhclasse.cc
Les dependències formen un graf. Els fitxers font són a baix, i l'executable, a dalt.
# L'executable s'enllaça a partir dels dos .o
main.exe: main.o classe.o
	g++ -o main.exe main.o classe.o

# main.cc inclou classe.hh: si canvia la classe, s'ha de tornar a compilar
main.o: main.cc classe.hh
	g++ -c main.cc

# classe.cc també inclou classe.hh
classe.o: classe.cc classe.hh
	g++ -c classe.cc
  • Cada .cc té la seva regla, que en fa el .o.
  • L’executable enllaça tots els .o.
  • classe.hh no té regla, perquè no es compila sol: només surt a les dependències dels .o dels fitxers que l’inclouen.
  • A la comanda només hi va el .cc. El #include ja fa que el compilador llegeixi classe.hh.

Com decideix què ha de refer

Cada fitxer porta la data de modificació: el dia i l’hora de l’últim cop que es va desar o es va generar.
make les compara per saber què ha quedat antic.

Per a cada objectiu de la llista de regles:

  1. S’assegura que les dependències estan al dia: si alguna té regla pròpia, primer aplica aquesta regla.
  2. Després mira les dates de modificació. Si l’objectiu no existeix, o alguna dependència és més nova que ell, executa la comanda. Si no, no fa res.

Així, quan canvies un fitxer, es refà únicament el que en depèn.
Si no has canviat res, make respon que l’objectiu ja és al dia (up to date) i no compila.

Cada .o depèn del seu .cc i de tots els .hh que inclou, directament o a través d’un altre .hh.

  • Si canvies classe.hh (per exemple, hi afegeixes un atribut), main.o s’ha compilat amb la versió d’abans de la classe i s’ha de refer. Per això classe.hh és a les dependències de main.o.
enllaçarcompilarmain.exemain.oclasse.omain.ccclasse.hhclasse.cc
Has canviat classe.hh: make refà els dos .o i torna a enllaçar.
Has canviat make refà
main.cc main.o i main.exe
classe.cc classe.o i main.exe
classe.hh main.o, classe.o i main.exe
Res Res: main.exe és al dia

Variables

Les opcions de compilació són llargues i volem que siguin les mateixes a totes les regles.
Per no escriure-les cada vegada, les podem guardar en una variable:

<NOM> = <valor>

A les comandes, $(<NOM>) es canvia pel valor.
Si vols canviar una opció, ho fas en un sol lloc.

Variable Què hi va
CXX El compilador: g++.
OPCIONS Les opcions de compilació: les del Jutge.

Les variables es posen a dalt de tot del Makefile:

# El compilador
CXX = g++
# Les opcions del Jutge
OPCIONS = -g3 -D_GLIBCXX_DEBUG -Wall -Wextra -Werror
	-Wno-sign-compare -std=c++17 -fno-extended-identifiers

main.o: main.cc classe.hh
	$(CXX) $(OPCIONS) -c main.cc

Abans d’executar la comanda, make canvia $(CXX) per g++ i $(OPCIONS) per totes les opcions.
La comanda que s’executa de debò és g++ -g3 -D_GLIBCXX_DEBUG … -c main.cc.

Objectius que no són fitxers

Fins ara, cada objectiu era un fitxer que la comanda creava.
Un objectiu també pot ser només un nom per a una tasca.

all: fer-ho tot

# Primera regla: és la que fa «make» sol
all: main.exe

all no és cap fitxer i no té comanda.
Només diu de què depèn: de l’executable.
Quan escrius make (o make all):

  1. make vol fer all. Com que no és un fitxer, mai no està fet.
  2. Per fer-lo, primer posa al dia les dependències: fa main.exe amb la seva regla, si cal.
  3. all no té comanda, així que s’acaba aquí.

Si tot ja és al dia, make respon Nothing to be done for 'all'.

Per a què serveix, si make sol ja feia la primera regla?

  • Deixa clar quin és el punt de partida, sigui quin sigui l’ordre de la resta de regles.
  • Si el programa té més d’un executable (per exemple, el programa i un de proves), all: main.exe proves.exe els fa tots dos amb un sol make.

clean: esborrar el que s’ha generat

# Esborra els .o i els executables (-f: no es queixa si no n'hi ha cap)
clean:
	rm -f *.o *.exe

clean no té dependències: la comanda s’executa sempre que escrius make clean.
Serveix per tornar a compilar de zero, o per deixar la carpeta neta abans de fer el .tar.

Alerta: al laboratori, algunes classes te les donen només com a .o. Si el teu clean fa rm -f *.o, també se les endurà. En aquest cas, a clean posa-hi només els .o que generes tu.

.PHONY

# all i clean no són fitxers
.PHONY: all clean

.PHONY diu a make que all i clean no són noms de fitxers.
Sense això, si mai hi hagués un fitxer que es digués clean, make clean el veuria, pensaria que ja està fet i no esborraria res.

Fer servir make

Des de la carpeta del Makefile:

make [<objectiu>]

Sense objectiu, fa el primer del fitxer.
Per això all es posa a dalt de tot.

Comanda Què fa
make Fa el primer objectiu del fitxer (normalment, all).
make <objectiu> Fa només aquest objectiu (i el que en depèn).
make clean Executa la regla clean.
make -n Escriu les comandes que faria, però no les executa.

Alerta: el fitxer s’ha de dir exactament Makefile (o makefile), sense extensió: ni Makefile.txt ni makefile.mk.

El Makefile sencer

Tot junt, un Makefile té aquesta forma:

# Variables
CXX = g++
OPCIONS = <opcions del Jutge>

# Primera regla: el que fa «make» sol
all: <programa>.exe

# L'executable: enllaça tots els .o
<programa>.exe: <fitxer>.o <fitxer>.o ...
	$(CXX) $(OPCIONS) -o <programa>.exe <fitxer>.o <fitxer>.o ...

# Una regla com aquesta per a cada .cc
<fitxer>.o: <fitxer>.cc <capçaleres que inclou>
	$(CXX) $(OPCIONS) -c <fitxer>.cc

# Esborra el que s'ha generat
clean:
	rm -f *.o *.exe

.PHONY: all clean

Errors habituals