Introduktion till Gnu make



Introduktion till make

Introduktion till hur man bygger egna makefiler

Copyright Kjell enblom, 2007 GNU Free Documentation License Version 1.1 or later.

Kort introduktion till make
Programmet make kan användas till mycket och en vanlig användning är
vid kompilering av program. Programmet make söker i katalogen man står
i efter en fil med namnet Makefile eller makefile. Vanligt brukar vara
att kalla den för Makefile.

En makefil ser ut på följande sätt.
Kod: Markera allt
target: fil1 fil2 fil3
        kommando

Target beror av ett antal filer. Här är det tre stycken, fil1, fil2,
fil3. Om någon av filerna har ändrats så ska kommandot "kommando"
köras. Observera att det är viktigt att det är en <TAB> först på
kommandoraden. Det är tack vare <TAB> som make ser att det är en
kommandorad.

Det make gör för att avgöra om en fil har ändrats är att den tittar på
filens ändringstid. Om en av beroendefilerna har en nyare ändringstid
än filen target då ska det eller de kommandon köras som hör ihop med
detta beroende. Det går alltså att ha flera kommandorader.

När nu denna första makefil är klar kan man stå i samma katalog som
filen ligger i och skriva make. Då tittar make i makefilen och tittar
efter den första targeten och de filer som den är beroende av. Om
någon beroendefil har en nyare tidsstämpel körs kommandona som hör
ihop med denna target. Det går även att skriva make target.

Om nu filerna fil1.c, fil2.c och fil3.c ska kompileras till program
kan makefilen se ut enligt följande.
Kod: Markera allt
program: fil1.c fil2.c fil3.c
<TAB>gcc fil1.c fil2.c fil3.c -o program


Nu går det att köra make eller make program.
Kod: Markera allt
[kjell-e@dumburk labbar]$ make program
gcc fil1.c fil2.c fil3.c -o program
[kjell-e@dumburk labbar]$


Om filen program inte finns har fil1.c, fil2.c och fil3.c nyare
tidsstämplar och då ska kommandoraden köras, d.v.s. programmet
kompileras. Om programmet är kompilerat och någon ändrar i en av
källkodsfilerna och på nytt kör make då kommer make att upptäcka att
en fil har ändrats och så kör den kompileringen.

Om inga filer är ändrade och man kör make kommer make att säga 'make:
"program" är färsk.' eller "make: `program' is up to date." beroende
på språkinställningar.

Nackdelen med att göra som ovan i sin makefil är att oavsett om en,
någon eller många av källkodsfilerna har ändrats så kommmer alla
källkodsfilerna att kompileras om. Om det är många och stora
källkodsfiler så innebär det att det kommer att ta onödigt lång tid.

Man kan då dela upp beroendena i flera delar. Från källkodsfiler till
objektfiler och från objektfiler till färdigt program.

Kod: Markera allt
program: fil1.o fil2.o fil3.o
         gcc fil1.o fil2.o fil3.o -o program

fil1.o: fil1.c
        gcc -c fil1.c
fil2.o: fil2.c
        gcc -c fil2.c
fil3.o: fil3.c
        gcc -c fil3.c


Om programmet nyligen är kompilerat och man ändrar i fil1.c och sedan
kör make då kommer make att upptäcka att fil1.c är nyare än fil1.o och
kompilerar fil1.c till fil1.o. Nu är fil1.o nyare än program så
programmet länkas ihop med den nya fil1.o och de redan existerande
fil2.o och fil3.o, d.v.s. kommandoraden "gcc fil1.o fil2.o fil3.o -o
program" körs. Filerna fil2.c, fil3.c kompileras inte om ifall de inte
har ändrats.


I make finns det inbyggda regler för hur en fil kompileras från
c-källkod till objektkod (och motsvarande för flera andra språk) så
dessa extrarader för fil1.o: fil1.c och tillhörande kommandon (och
motsvarande för fil2 och fil3) behövs inte. Makefile blir därmed kortare.
Kod: Markera allt
program: fil1.o fil2.o fil3.o
         gcc fil1.o fil2.o fil3.o -o program



Man kan även ha variabler i make och dessa skrivs som
VARIABEL = värde1 värde2 värde3 värde4 värde5
För att sedan använda VARIABEL och dess värde skriver man $(VARIABEL) .

Exempel på en Makefile:
Kod: Markera allt
CC = gcc
PROGRAM = program
OBJECTS = fil1.o fil2.o fil3.o
$(PROGRAM): $(OBJECTS)
         $(CC) $(OBJECTS) -o $(PROGRAM)

Notera att här används variabeln CC för namnet på kompilatorn. Om man
byter kompilator så behöver man bara ändra på ett ställe i makefilen och
detta oavsett hur många kommandorader med kompilatorns namn som
förekommer.

Det är även vanligt att lägga flaggorna till kompilatorn i en eller
flera variabler.
Kod: Markera allt
CC = gcc
PROGRAM = program
CFLAGS = -g -O2 -Wall -ansi -pedantic
LFLAGS = -L libkatalog -lm -lfoo -lbar
SOURCE = fil1.c fil2.c fil3.c
$(PROGRAM): $(SOURCE)
         $(CC) $(CFLAGS) $(SOURCE) $(LFLAGS) -o $(PROGRAM)


I make finns det även en form av inbyggda variabler.

$@ ger namnet på target

$^ ger alla beroendefiler

$< ger första beroendefilen


Exempel på en Makefile:
Kod: Markera allt
CC = gcc
PROGRAM = program
OBJECTS = fil1.o fil2.o fil3.o
$(PROGRAM): $(OBJECTS)
         $(CC) $^ -o $@



Som har visats tidigare så kan en target vara beroende av en annan
target (se makefilen med program som var beroende av objektfiler som i
sin tur var targets beroende av c-källkodsfiler).
Exempel på en del av en Makefile:
Kod: Markera allt
CC = gcc
PROGRAM = program
OBJECTS = fil1.o fil2.o fil3.o

install: $(PROGRAM)
         cp  $(PROGRAM)  /usr/local/bin

$(PROGRAM): $(OBJECTS)
         $(CC) $^ -o $@



Det går även att ha tomma targets. Kommandona tillhörande dessa körs
då alltid. Exempel på en del av en Makefile:

Kod: Markera allt
clean:
        \rm -f *~ *.o

Genom att skriva make clean kommer alla filer som ligger i den här
katalogen och som har namn som slutar på ~ respektive slutar på .o att
raderas.

Kod: Markera allt
[kjell-e@dumburk labbar]$ make clean
\rm -f *~ *.o
[kjell-e@dumburk labbar]$

Det finns dock ett problem med det här. Om det finns en fil med namnet clean kommer denna target inte att utföras och filerna kommer inte att raderas. Det går dock att åtgärda genom att ange att denna target ska vara phony. För det använder man .PHONY.

Exempel:
Kod: Markera allt
.PHONY: clean
clean:
        \rm -f *~ *.o



Om man på en kommandorad i en makefil vill ha ett $-tecken så måste
det skrivas som $$. I annat fall kommer make att försöka tolka
$-tecknet.

Om man vill att kommandoraden inte ska skrivas ut innan den körs så
går det att åstadkomma med ett @ i början av kommandoraden.
Kod: Markera allt
CC = gcc
PROGRAM = program
OBJECTS = fil1.o fil2.o fil3.o
$(PROGRAM): $(OBJECTS)
         @ $(CC) $^ -o $@




Om man har flera källkodskataloger och en makefil i varje katalog och
vill köra alla dessa från en huvudkatalog så går det att åstadkomma
genom att låta make köra cd till subkatalogen och där köra make. Allt
detta behöver man göra i ett subskal. Ett subskal fås genom att skriva
kommandona inom parenteser.

Kod: Markera allt
all:
        (cd subkatalog; make)


Om det är många subkataloger är det enklare att använda en loop i
makefilen. Denna loop skapas med for. Exempel:
Kod: Markera allt
SUBKATALOGER = katalog1 katalog2 katalog3 katalog4
all:
        @for SUBKATALOG in $(SUBKATALOGER); do \
             (cd $$SUBKATALOG; make ) || exit 1; \
        done

Variabeln SUBKATALOGER innehåller namnen på katalogerna. For loopar
över dessa och ger SUBKATALOG namnet på en katalog i taget för varje
varv. För var och en av dem körs en cd och make och om make inte gick
bra avslutas allt med en exit 1.


Att göra på det här sättet med att loopa över subkataloger har dock en nackdel nämligen att make inte kan parallellisera arbetet.


Ett bättre alternativ är att göra enligt följande:
Kod: Markera allt
SUBDIRS=foo bar gazonk

.PHONY: subdirs $(SUBDIRS)

subdirs: $(SUBDIRS)

$(SUBDIRS):
     $(MAKE) -C $@



Variabeln SUBDIRS innehåller namnen på subkatalogerna. Vi sätter upp en target subdirs som är beroende av subkatalogerna i variabeln SUBDIRS. Sedan sätter vi upp target för de respektive subkatalogerna som finns i variabeln SUBDIRS. För dessa kör vi make (som finns i variabeln MAKE). Make kommer att gå till den aktuella katalogen (-C) och köra make där. Notera att vi behöver sätta subdirs respektive subkatalogerna till .PHONY targets. Annars kommer de inte att köras.

Med en top-makefil som ser ut på detta sätt kan man till make ange -j TAL för att få make att köra parallelliserat. Talet anger antal jobb (kommandon) som ska köras parallellt.
Exempel:
make -j 5



Om man har flera targets i subkatalogerna och vill kunna köra dessa från en top-makefil så går det att göra genom att definiera toptargets, spara alla subkataloger i en variabel, t.ex. med hjälp av filmatchningsmönster (wildcards), och sätta upp targets på liknande sätt som ovan. Här skickar vi även med MAKECMDGOALS när vi anropar make på subkatalogerna. Variabeln MAKECMDGOALS sätts automatiskt till det argument vi ger till make på kommandoraden.

T.ex. för
make cleanall
sätts MAKECMDGOALS till cleanall.

Kod: Markera allt
TOPTARGETS := all clean cleanall

SUBDIRS := $(wildcard */.)

$(TOPTARGETS): $(SUBDIRS)
$(SUBDIRS):
     $(MAKE) -C $@ $(MAKECMDGOALS)

.PHONY: $(TOPTARGETS) $(SUBDIRS)





Tidigare nämndes att det finns inbyggda regler i make. Det går att
skapa egna liknande regler. Om man t.ex. vill skapa .f-filer från
.e-filer så använder man % och skriver:
Kod: Markera allt
%.f: %.e
     kommando $< -o $@

En fil som slutar med .f är beroende av en fil med samma namn men som
slutar med .e . Om filen som slutar på .e är nyare ska kommandoraden
köras vilken kompilerar denna enda .e-fil.


För att skapa objektfiler från c-källkodsfiler skulle det då bli.
Kod: Markera allt
%.o: %.c
     gcc -c $< -o $@
Användarvisningsbild
kjell-e
 




Copyright © 2010-2020 Kjell Enblom.
This document is covered by the GNU Free Documentation License, Version 1.3

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".