Hoe om ‘n makefile te skryf

Wat is ‘n makefile en waarom sal jy een wil maak? Goeie vraag, maar voor ons begin weet dat ek moontlik niks meer as jy weet en hierdie inskrywing net die stappe meld wat ek geneem het om ‘n projek in beide Windows en Linux saam te stel.

Wat is ‘n makefile?

‘n Makefile is ‘n lêer wat ‘n program vertel hoe om ‘n program te bou en te skakel. Meeste IDEs soos Eclipse en Microsoft Visual Studio doen dit agter die skerms vir jou deur inligting soos the lêers waaruit ‘n projek bestaan, gelyste biblioteke en ander ingevulde vorms te gebruik. Wanneer jy egter ‘n program sonder die hulp van ‘n IDE wil bou is jy vas. Dit is hier waar die makefile jou help en jou toelaat om jou program op ‘n eksterne rekenaar te bou ens. Meer inligting soos altyd by Wikipedia.

Vir groot programme is daar ook CMake wat vir jou ‘n makefile skep, maar ek dink nie dit is vir nou nodig om daarna te kyk.

Waar om te begin?

Hierdie voorbeeld gebruik die minGW C++ saamsteller en make.exe en is ingesluit wanneer MinGW afgelaai word. Nadat MinGW geinstalleer is maak seker dat g++.exe en make.exe toegangbaar is op die “path”. Aanvanklik kyk ons hoe om die makefile vir Windows te skep en verander dit dan aan die einde om ook Linux te ondersteun.

Alhoewel ek MinGW gebruik verskaf Microsoft ook nmake wat veronderstel is om dieselfde te doen.

Skep makefile-raamwerk

Skep ‘n leë lêer. Jy kan dit noem wat jy ook al wil, maar die eenvoudigste is om dit makefile te noem sonder enige uitbreiding. Deur dit te noem kan jy slegs make roep in die lêer en makefile sal automaties gebruik word. Indien jy verkies om dit iets ander te noem kan jy dit roep met

make -f jounaam.xyz

waar jounaam.xyz die naam is wat jy gekies het vir jou makefile. Vir die res van hierdie artikel neem ek aan jou makefile is genoem ‘makefile’ en jy het nie nodig om vir make die naam te spesifiseer (so jy tik slegs ‘make’ en die lêer makefile in die gids waar jy is word automaties gebruik).

Tyd om die makefile-lêer te wysig en te vertel wat jy wil bou. Om hiermee te help beveel ek aan om Notepad++ te gebruik (of Kate in Linux) aangesien dit onder “Language” makefile as ‘n opsie het. Dit formateer die teks vir jou en maak dit makliker om te sien wat jy doen.

Die struktuur van die makefile bestaan uit afdelings van reëls. ‘n Spesifieke reël word geroep deur

make naamVanDieReël

Indien geen reël gespesifiseer is wanneer make geroep word, word die eerste reël gebruik, in die geval van die voorbeeld hier onder die “all”-reël. Hierdie voorbeeld bou ‘n enkele leër en verskaf ‘n reël “clean” om dit weer uit te wis:

# build an executable named myProg from myProg.c
all:
    g++ -g - Wall-o myProg myProg.cpp

clean:
    $(RM) myProg.exe

Hier kan ‘make’ geroep word om die program te bou en ‘make clean’ om die geboude resultaat te wis. Let wel, dit is belangrik dat die bevel na die reël se naam altyd volg na ‘n tab-karakter. Ook, maak seker daar is nie enige spasie-karakters aan die einde van lyne. Make is vreeslik vol nonsens wanneer dit kom by tab-of spasie-karakters. Gebruik hulle slegs waar hulle nodig is en maak seker daar is geen onnodige karakters.

Brei raamwerk uit

Die eenvoudige raamwerk is goed en wel maar beteken niks. Om dieselfde te doen sonder ‘n makefile is maklik en neem nie soveel langer. Om dit werklik nuttig te maak het ons nodig om meer leërs te bou, hulle te verbind (“link”), na eksterne biblioteke te verwys en ook die makefile versoenbaar te maak met beide Windows en Linux.

Konstantes

Die eerste voorbeeld kan meer nuttig gemaak word deur die saamsteller (“compiler”), teiken en opsies deur konstantes te spesifiseer in stede van eksplisiet. Die konstantes word in die makefile-lêer gespesifiseer voor enige van die reëls en kan dan gebruik word as deel van die reëls deur daarna te verwys met $(konstantes). Deur dit te gebruik kan die oorspronklike voorbeeld verander word na die volgende

#Specify compiler
CC = g++

#Compiler flags
# -g adds debugging information to the executable file
# -Wall turn on most but not all compiler warnings
# -O or -O2 - turn on optimizations# -o <name> - name of the output file
# -c - output an object file (.o)
# -I<include path> - specify an include directory
# -L<library path> - specify a lib directory
# -l<library> - link with library lib<library>.a
CFLAGS =  -g -Wall

#Target executable
TARGET = myProg

# build an executable named myProg from myProg.c
all:
    $(TARGET) $(CC) $(CFLAGS) -o $(TARGET) $(TARGET).cpp

clean:
    $(RM) $(TARGET).exe

Veelvuldige leërs

Volgende wil ons die makefile uitbrei om veelvuldige lêers te bou. Dit vereis dat elk van die leërs eers individueel gebou word en die geboude objeklêers dan gebruik word om die finale program te bou.

Vir elk van die lêers skep ons ‘n reël om die individuele lêer te bou

# Build myFunctions.o, myFunctions.cpp myFunctions.hpp
myFunctions.o: myFunctions.cpp myFunctions.hpp
    $(CC) $(CFLAGS) -c myFunctions.cpp

Dit skep die objeklêer myFunction.o wat dan kan gebruik word om die finale program te bou met die volgende inskrywing. Hier neem die saamsteller the objeklêers, verbind die nodige funksies en bou die finale uitvoerbare lêer.

# Build complete program from multiple files.
# To create executable myProg we need 
#   object files myProg.o and myFunctions.o
$(TARGET): myProg.o myFunctions.o 
    $(CC) $(CFLAGS) -o $(TARGET) myProg.o myFunctions.o

Op die oomblik word al die objetlêers gebou in dieselfde gids as bronkode. Om dit makliker te maak om hierdie lêers te beheer maak dit sin om te bou na ‘n spesifieke gids. Vir dit benodig ons ‘n nuwe konstante OBJDIR, ‘n nuwe reël om die bougids te skep indien dit nie reeds bestaan en ook ‘n verandering in die boureël om na hierdie gids te bou. Die inskrywing wat by die huidige makefile gevoeg moet word is dan soos volg:

#Specify object folder
OBJDIR = build

# Create object directory if it does not exist
$(OBJDIR):
    mkdir $(OBJDIR)

# Specify folder and name to build object to
# Build myFunctions.o, requires myFunctions.cpp and myFunctions.hpp
myFunctions.o: myFunctions.cpp myFunctions.hpp 
    $(CC) $(CFLAGS) -c -o $(OBJDIR)\myFunctions.o myFunctions.cpp

#Update clean to remove folder
clean:
    $(RM) -r -f $(OBJDIR)
    $(RM) $(TARGET).exe *.o *~

Hierdie werk goed, maar vereis dat elke lêer wat gebou moet word gespesifiseer word in die makefile. Dit maak dit moeiliker om die makefile te onderhou, maar gee baie fyn beheer oor hoe en wanneer iets gebou word. Om dit makliker te maak gaan ons die makefile opdateer om alle C++ lêers in ‘n gids met die naam “src” te bou na die “build”-gids. Hiervoor kry ons ‘n lys van C++ lêers waarvan ons ‘n lys objeklêers skep in die “build”-gids.

dirstruct

Eers moet moet ons al die bronkode-lêers vind wat ons wil bou in die src-gids. Dit word gedoen met

SRCDIR = src
SRCLIST = $(wildcard $(SRCDIR)/*.cpp)

Daarna wil ons ‘n lys van objeklêers maak wat ons van hierdie bronkode gaan bou. Dit doen ons deur ‘n lys te skep waar die *.cpp uitbreiding van die SRCLIST vervang word met *.o

OBJLIST = $(addprefix $(OBJDIR)/, $(patsubst %.cpp, %.o, $(notdir $(SRCLIST))))

Nou skep ons ‘n reël wat enige bronkode-lêer sal bou. Dit gaan gebruik word om deur die lys wat ons geskep het te gaan en een vir een al die lêers te bou

# Compile all .o object files in $(OBJDIR) from .cpp files
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp 
    $(CC) $(CFLAGS) -c

lt; -o $@

En die hoof bou-reël word opgedateer om dit te gebruik

# Build complete program from multiple files.
$(TARGET): $(OBJLIST) 
    $(CC) $(CFLAGS) -o $(TARGET) $^

Die volledige makefile lyk dan soos volg:

#Specify compiler
CC = g++

#Compiler flags
# -g adds debugging information to the executable file
# -Wall turn on most but not all compiler warnings
# -O or -O2 - turn on optimizations
# -o <name> - name of the output file
# -c - output an object file (.o)
# -I<include path> - specify an include directory
# -L<library path> - specify a lib directory
# -l<library> - link with library lib<library>.a
CFLAGS =  -g -Wall

#Target executable
TARGET = myProg

#Specify source folder#Specify source folder
SRCDIR = src

#Specify object folder
OBJDIR = build

# Create list of object files that needs to be compiled by
#  taking list of all .cpp files and replacing .cpp with .o
#  and add prefix $(OBJDIR)/ to it
SRCLIST = $(wildcard $(SRCDIR)/*.cpp)
OBJLIST = $(addprefix $(OBJDIR)/, $(patsubst %.cpp, %.o, $(notdir $(SRCLIST))))

# Default/First rule for building complete program with dependencies
default: $(OBJDIR) $(TARGET)

# Build complete program from multiple files.
$(TARGET): $(OBJLIST) 
    $(CC) $(CFLAGS) -o $(TARGET) $^

# Create object directory if it does not exist
$(OBJDIR): mkdir $(OBJDIR)

# Compile all .o object files in $(OBJDIR) from $(SRCDIR)/.cpp files
$(OBJDIR)/%.o: $(SRCDIR)/%.cpp 
    $(CC) $(CFLAGS) -c

lt; -o $@

#Build myProg.o, requires myProg.cpp, myProg.hpp,
# myFunctions.cpp and myFunctions.hpp
myProg.o: myProg.cpp myProg.hpp myFunctions.cpp myFunctions.hpp
$(CC) $(CFLAGS) -c myProg.cpp

#Build myFunctions.o, requires myFunctions.cpp myFunctions.hpp
myFunctions.o: myFunctions.cpp myFunctions.hpp
$(CC) $(CFLAGS) -c myFunctions.cpp

clean:
$(RM) -r -f $(OBJDIR)
$(RM) $(TARGET).exe *.o *~

Biblioteke

Volgende wil ons graag biblioteke gebruik. Die biblioteke wat ons gaan gebruik word almal gestoor waar MinGW hulle in die hande kan kry. In my geval is dit in \MinGW\lib\ en die ooreenkomstige .h lêers in \MinGW\include. In my eenvoudige program gebruik ek die GLEW biblioteek met die naam GLEW32. Ons het nodig om slegs die hoof bou bevel op te dateer om dit te gebruik. Dit word gedoen deur die -lBIBLIOTEEK by die g++ bevel te voeg. Die opdateerings is soos volg:

#Libraries for linker
LIBS = -lGLEW32

#Build complete program from multiple files.
$(TARGET): $(OBJLIST) 
    $(CC) -o $@ $^ $(CFLAGS) $(LIBS)

Veelvuldige omgewing ondersteuning

Laastens wil ons graag in beide Windows en Linux bou. Meeste van die makefile behoort dieselfde te wees, maar hier en daar sal daar verskille wees hoe gebou word. In hierdie voorbeeld is die verskil tussen Windows en Linux die naam van die biblioteek, in Windows is die GLEW32, maar in Linux GLEW. Om beide te ondersteun word konstantes verklaar vir beide Windows en Linux. Wanneer die biblioteke benodig word om te bou word die regte stel biblioteke gebruik afhangende van die stelsel waarop gebou word.

#Windows libraries for linker
LIBSWIN = -lGLEW32

#Linux libraries for linker
LIBSLIN = -lGLEW

#Build complete program from multiple files.
$(TARGET): $(OBJLIST)
ifeq ($(OS),Windows_NT) 
#Windows stuff 
    $(CC) -o $@ $^ $(CFLAGS) $(LIBSWIN)
else 
#Linux stuff 
    $(CC) -o $@ $^ $(CFLAGS) $(LIBSLIN)
endif

Slot

En dit is dit wat ek op die oomblik benodig. Soos meer benodig word sal hierdie inskrywing uitgebrei word. Die volledige makefile is hier beskikbaar >> SKAKEL

One thought on “Hoe om ‘n makefile te skryf”

Leave a Reply