Colleges will be back in session in a month or two. When I first started school, I was forced to use Unix for all CS classes, but given very little instruction in it. Very “sink-or-swim”. I actually still have a page of notes where I wrote down basic commands, such as ‘ls’ and ‘pwd’, from a lecture given on the first day. After that first day, you were on your own.
This same philosophy applied to our projects. Projects typically consisted of 5 or more files and it quickly became tedious to compile and link all those files by hand. We were given no hints to use Make to assist us. After discovering this tool, it greatly increased my productivity. Because of the gains I saw, I thought I’d write a few tutorials on using Make. Hopefully, it will help a beginning user to get accustomed with the tool and point them in the right direction to learn more.
This is Part 2 of my make tutorial.
However, the techniques I covered were basic and static; if anything in your project changed, you had to manually edit your makefile quite a few times. In this tutorial, I hope to show you how to use make in more of a dynamic way and minimize the number of changes you have to make when changing your project.
First things first though. I expect that you have read and (at least somewhat) understand Part 1 of this tutorial. I’m not going to rewrite my first tutorial, but I will try to point out important points as needed.
So, what can you expect to get out of this tutorial? Here’s a list:
- User created variables
Yes, only one bullet point (Why even use a list then?). Variables are a pretty broad topic so I’m going to use this tutorial just for them.
Variables are an important part of other languages, but you may be wondering how variables could ever be useful in something like Make. Well, you can find plenty of uses for them!
Here’s an example makefile for us to look at:
finalOutput: part1 part2 part3
gcc -o finalOutput firstFile.o secondFile.o thirdFile.o
gcc -c firstFile.cpp
gcc -c secondFile.cpp
gcc -c thirdFile.cpp
rm demo *.o
This makefile looks pretty reasonable, but let me ask you something. What if you want to add debugging information? You do this by adding the -g flag after any gcc command. That means you have to manually make 4 changes every time you want to switch modes. This will quickly get tedious as you won’t always want debug information, but sometimes you will.
The solution to this problem is to create a variable to store your flags and use that as needed. That way, if you want to change your flags you only need to make 1 change, rather than 4. Or even better, you can create a target to dynamically change the variables you’re working with, such as making a special ‘debug’ target. Let’s take a look at what I’m talking about.
finalOutput: firstFile.o secondFile.o thirdFile.o
gcc -o finalOutput *.o
rm finalOutput *.o
The first change is the ‘CFLAGS=’ and ‘CC=gcc’ lines at the top of the file. These are variable declarations. Note how CFLAGS is initially declared to be empty. That’s perfectly valid, since when using a variable, make simply does text substitution, so the variable just expands to blank space.
You may notice that nowhere in the makefile are the CFLAGS or CC variables actually type. This is because CC and CFLAGS are special variables that make uses when compiling C programs. For C programs, make will automatically run the command stored in the CC variable and pass it the flags in the CFLAGS variable. For C++, the equivalent variables are CXX and CXXFLAGS.
The debug target is interesting to look at, since it contains 2 lines. What this target does is add the -g flag to CFLAGS, which will include debugging information in the executable. It then runs the ‘all’ target and compiles as normal but with the new flags. Let’s look at this makefile’s output:
$ make debug$ make debug gcc -g -c -o firstFile.o firstFile.c gcc -g -c -o secondFile.o secondFile.c gcc -g -c -o thirdFile.o thirdFile.c gcc -o finalOutput *.o $
Notice how the -g flag is now automatically included! It will not be present if you just run ‘make’ though.
Let’s take a look at how you can use variables to keep a list of your source files, making it even easier to keep track of your projects.
- Let’s delete the part1, part2, part3, and finalOutput targets completely.
- Create a variable, OBJECTS, and set it equal to all your object files.
- Now, add a dependency to the all target to the OBJECTS variable.
After doing that, your makefile will be much simpler and look like:
OBJECTS=firstFile.o secondFile.o thirdFile.o
gcc -o finalOutput $(OBJECTS)
rm finalOutput *.o
Note the syntax to expand a variable is $(VARIABLENAME).
This makefile will produce the exact same results as the previous one, but this one is a whole lot easier to maintain, as I’m sure you can see. Now when you want to add a new file to your makefile, just change the OBJECTS variable.
Originally, I planned to cover more topics in this tutorial, but I don’t want to overload you too much. There’s actually quite a bit more to using variables in makefiles, but this tutorial covered common uses for them. This should help get you started using variables in your programs. For more details, you can read the documentation.
Next time, I’m going to cover variables again, but a different kind of variable. This new type of variable will make your makefile even more dynamic and powerful. (Oooo, mysterious!)