C++ Tutorial - gnu make - 2020
A simple makefile consists of rules with the following syntax:
target ... : prerequisites (dependencies) ... recipe (system command) ...
By default, when make looks for the makefile, it tries the following names, in order: GNUmakefile, makefile and Makefile.
Let's start Makefile with 4 files:
myprogram : main.o aa.o bb.o cc.o g++ -o myprogram main.o aa.o bb.o cc.o main.o : main.cpp header1.h g++ -c main.cpp aa.o : aa.cpp header1.h header2.h g++ -c aa.cpp bb.o : bb.cpp header2.h g++ -c bb.cpp cc.o : cc.cpp header3.h g++ -c cc.cpp clean : rm myprogram \ main.o aa.o bb.o cc.
The make starts with the first target (not targets whose names start with "."). In the example above, the default goal is to update the executable program myprogram; therefore, we put that rule first. When we issue the command:
makemake reads the makefile in the current directory and starts by processing the first rule. In our case, this rule is for relinking myprogram. However, before make can fully process this rule, it must process the rules for the files that myprogram depends on, which in this case are the object files. Each of these files is processed according to its own rule. These rules are updating each .o file by compiling its source file. The recompilation should be done if the source file, or any of the header files named as prerequisites, is more recent than the object file, or if the object file does not exist.
The other rules are processed because their targets appear as prerequisites of the goal. If some other rule is not depended on by the goal, that rule is not processed, unless we tell make to do so with a command such as make clean.
After recompiling whichever object files needed, make decides whether to relink myprogram. This must be done if the file does not exist, or if any of the object files are newer than it. If an object file was just recompiled, it is now newer than myprogram, so myprogram is relinked. Thus, if we change the file aa.cpp and run make, make will compile that file to update aa.o, and then link myprogram. If we change the file header2.h and run make, make will recompile the object files aa.o and bb.o and then link the file myprogram.
In short, to update myprogram, we just issue make, to clean *.o and myprogram, we issue make clean.
But there are repeated files, and this makes the make error prone when other files are added later. So, we need to use variables:
#Makefile case 1 OBJECTS = main.o aa.o bb.o cc.o myprogram : $(OBJECTS) g++ -o myprogram $(OBJECTS) main.o : main.cpp header1.h g++ -c main.cpp aa.o : aa.cpp header1.h header2.h g++ -c aa.cpp bb.o : bb.cpp header2.h g++ -c bb.cpp cc.o : cc.cpp header3.h g++ -c cc.cpp clean : rm myprogram \ $(OBJECTS)
Acutally, make has an implicit rule (when and how to remake files based on their names) for updating a *.o file from a correspondingly named *.cpp file, we do not have to put recipes for the compiler. For example, to make main.o we do not have to specify g++ -c main.cpp:
#Makefile case 2 OBJECTS = main.o aa.o bb.o cc.o myprogram : $(OBJECTS) g++ -o myprogram $(OBJECTS) main.o : header1.h aa.o : header1.h header2.h bb.o : header2.h cc.o : header3.h clean : rm myprogram \ $(OBJECTS)
To prevent make from getting confused by an actual file called clean, me may use .PHONY:
#Makefile case 3 OBJECTS = main.o aa.o bb.o cc.o myprogram : $(OBJECTS) g++ -o myprogram $(OBJECTS) main.o : header1.h aa.o : header1.h header2.h bb.o : header2.h cc.o : header3.h .PHONY : clean clean : -rm myprogram $(OBJECTS)
The "-" in -rm causes make to continue in spite of errors from rm.
The following Makefile is using more variables including automatic variables.
#Makefile case 4 CC = g++ CFLAGS = -c SOURCES = main.cpp aa.cpp bb.cpp cc.cpp OBJECTS = $(SOURCES:.cpp=.o) EXECUTABLE = myprogram all: $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE) : $(OBJECTS) $(CC) $(OBJECTS) -o $@ .cpp.o: *.h $(CC) $(CFLAGS) $< -o $@ clean : -rm -f $(OBJECTS) $(EXECUTABLE) .PHONY: all clean
If we do make or make all, we get:
main.cpp -o main.o g++ -c aa.cpp -o aa.o g++ -c bb.cpp -o bb.o g++ -c cc.cpp -o cc.o g++ main.o aa.o bb.o cc.o -o myprogram
If we do not specify all in the makefile, the make will only work on the first target it encounters in the makefile.
For detail explanation, please see Makefile example from Google AI Ants, and samples are available, makefile_sample.tar.gz.
If the name of the above file is neither makefile nor , the make will give the error looks like this:
$ make make: *** No targets specified and no makefile found. Stop.
Suppose the make file name is Makefile1, we should use -f:
$ make -f Makefile1
Here we added install option to the Makefile:
# compiler CC = g++ # install dir INSTDIR = /usr/local/bin # include dir INCLUDE = . # for debug CFLAGS = -g -c -Wall # for release # CFLAGS = -O -c -Wall SOURCES = main.cpp aa.cpp bb.cpp cc.cpp OBJECTS = $(SOURCES:.cpp=.o) EXECUTABLE = myprogram all: $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE) : $(OBJECTS) $(CC) $(OBJECTS) -o $@ .cpp.o: *.h $(CC) $(CFLAGS) $< -o $@ clean : -rm -f $(OBJECTS) $(EXECUTABLE) .PHONY: all clean install: $(EXECUTABLE) @ if [ -d $(INSTDIR) ]; then \ cp $(EXECUTABLE) $(INSTDIR); \ chmod a+x $(INSTDIR)/$(EXECUTABLE); \ chmod og-w $(INSTDIR)/$(EXECUTABLE); \ echo "$(EXECUTABLE) is installed in $(INSTDIR)"; \ else \ echo "Warning! $(INSTDIR) does not exist"; \ fi
The make knows dependency, it should do something about the files that myprogram depends upon before install the target. The rule for install is written in shell script. To execute the rules specified by the shell script, make invokes a shell for each rule (recipe). The @ in front of the if statement, blocks echo for each command. Actually the back slash(\) makes enables the one time invoke of the shell by making the script one line.
$ make install g++ -g -c -Wall main.cpp -o main.o g++ -g -c -Wall aa.cpp -o aa.o g++ -g -c -Wall bb.cpp -o bb.o g++ -g -c -Wall cc.cpp -o cc.o g++ main.o aa.o bb.o cc.o -o myprogram myprogram is installed in /usr/local/bin
If we want to constrain the execution of script, i.e., for conditional execution, we can use &&:
install: $(EXECUTABLE) @ if [ -d $(INSTDIR) ]; \ then \ cp $(EXECUTABLE) $(INSTDIR) && \ chmod a+x $(INSTDIR)/$(EXECUTABLE) && \ chmod og-w $(INSTDIR)/$(EXECUTABLE) && \ echo "$(EXECUTABLE) is installed in $(INSTDIR)"; \ else \ echo "Warning! $(INSTDIR) does not exist"; \ fi
Let's use make with no Makefile.
/* main.cpp */ #include <stdio.h> int main(int argc, char* argv[]) { return 0; }
If we run make:
$ make main g++ -c -o main.o main.cpp cc main.o -o main
No problem!
That's because make has lots of internal rules, and it knows how to invoke compiler as we see from the example above. It is called inference rule. Since the rules use macro, we can change the behavior by assigning a new value to the macro.
Let's look into the internal things using make -p:
$ make -p ... # default LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH) # default OUTPUT_OPTION = -o $@ # default COMPILE.cpp = $(COMPILE.cc) # makefile MAKEFILE_LIST := # automatic @F = $(notdir $@) # environment HISTCONTROL = ignoredups # environment USERNAME = KHong # default LINK.p = $(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) # environment DBUS_SESSION_BUS_ADDRESS = unix:abstract=/tmp/dbus-RoRanvDlfE,guid=c1626f5713375 4705bb66b075169bb24 # default CC = cc # default CHECKOUT,v = +$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@) ...
In the previous section, we used the following:
SOURCES = main.cpp aa.cpp bb.cpp cc.cpp OBJECTS = $(SOURCES:.cpp=.o)
It enables generating .o files from .cpp files.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization