Complex Recipes

This page contains examples of what else can be done with Makefiles. These techniques are not required for your assignment Makefiles.


One of the things make can do is only rebuild files that have changed.

To use this feature, we need to explicitly compile individual files in to object code, then merge the results. Here is a recipe that does this:

all:
	#make a directory to hold the build product
	mkdir -p build
	#build MyProgram.exe to that directory
	g++ -g -c -o build/main.o main.cpp
	g++ -g -c -o build/Answer.o Answer.cpp
	g++ -g -o build/MyProgram.exe build/main.o build/Answer.o

The two lines that include -c say to "only compile this code, do not link it into an executable yet". Each line compiles one of the .cpp files into an object (.o) file. The final line builds the executable from the two object files made on the preceding lines.

That recipe still builds all of the files at once, every time we run the recipe. To compile individual parts, we need to break it up into multiple rules:

all: makeDirectory buildMain buildAnswer
	g++ -g -o build/MyProgram.exe build/main.o build/Answer.o

buildMain: 
	g++ -g -c -o build/main.o main.cpp

buildAnswer:
	g++ -g -c -o build/Answer.o Answer.cpp

makeDirectory:
	mkdir -p build

Now we can build just one of the object files by calling something like:

make -f MyMake buildMain

If we run the all rule (or don't specify a rule and get it by default), the prerequisites say we need to first run makeDirectory the two build____ rules.

But to build all, this still will recompile everything. The next step is to rename the rules so they match the name of the file they build. When make encounters a rule, it checks to see if there is a file with that name. If so, it does not run the rule.

build/MyProgram.exe: makeDirectory build/main.o build/Answer.o
	g++ -g -o build/MyProgram.exe build/main.o build/Answer.o

build/main.o:
	g++ -g -c -o build/main.o main.cpp

build/Answer.o:
	g++ -g -c -o build/Answer.o Answer.cpp

makeDirectory:
	mkdir -p build

Now if you run the default rule to make the full program, it finds and uses the existing .o files. You should only see one g++ command be executed.

But there is a problem - if you change a file, the build will not know that it needs to recompile that file. Try changing main to print something extra, running make, and then running the program. You won't see the new code as it is being ignored.

To fix this, we need to mark each rule with its prerequisites. For the object files, the prerequisites are the .cpp files:

build/MyProgram.exe: makeDirectory build/main.o build/Answer.o
	g++ -g -o build/MyProgram.exe build/main.o build/Answer.o

build/main.o: main.cpp
	g++ -g -c -o build/main.o main.cpp

build/Answer.o: Answer.cpp
	g++ -g -c -o build/Answer.o Answer.cpp

makeDirectory:
	mkdir -p build

The line build/main.o: main.cpp says that build/main.o depends on main.cpp. If main.cpp is more recently changed than build/main.o, we need to rebuild it.

Now, if you run the makefile, the default rule knows that build/main.o is a prerequisite and so it evaluates that rule. Make can tell that build/main.o depends on main.cpp which has been updated, so it rebuilds that. Now, since build/main.o is newer than build/MyProgram.exe, make will rebuild that as well. However Answer.o, which was never changed, gets reused as is.

We also might want to add any .h files that .cpp files include as prerequisites - that way if we change them, the .cpp gets recompiled even if we don't change the .cpp files. This is especially important if the .h file has templated code that may not exist in a .cpp file!

build/MyProgram.exe: makeDirectory build/main.o build/Answer.o
	g++ -g -o build/MyProgram.exe build/main.o build/Answer.o

build/main.o: main.cpp Answer.h
	g++ -g -c -o build/main.o main.cpp

build/Answer.o: Answer.cpp Answer.h
	g++ -g -c -o build/Answer.o Answer.cpp

makeDirectory:
	mkdir -p build

That makefile may seem a bit tedious. And it is. Imagine writing a separate rule for each one of 300 .cpp files for how to turn it into a .o file. But there are tricks that can be used to automatically define a rule for any .cpp file to turn it into a .o file. Here is such a recipe. The weird rule in the middle handles both .o files.

build/MyProgram.exe: makeDirectory build/main.o build/Answer.o
	g++ -g -o build/MyProgram.exe build/main.o build/Answer.o

build/%.o: %.c
	g++ -c -o [email protected] $<

makeDirectory:
	mkdir -p build

If you want to learn how tricks like this work, try this detailed tutorial: https://makefiletutorial.com/