Computer Science Canada

Make & Makefiles

Author:  md [ Thu Jan 18, 2007 6:27 pm ]
Post subject:  Make & Makefiles

Make

Make is a nice little function that makes it easy to manage the build process of your programming project. While it can be used for many languages, makefiles are most commonly applied to C/C++ projects.

Origins
Wikipedia wrote:
There are now a number of dependency-tracking build utilities, but make is one of the most wide-spread, primarily due to its inclusion in Unix, starting with the PWB/UNIX 1.0, which featured a variety of tools targeting software development workloads. It was originally created by Stuart Feldman in 1977 at Bell Labs. In 2003 Dr. Feldman received the ACM Software System Award for the invention of this important tool.
Before make's introduction, the Unix build system would most likely consist of "make" and "install" shell scripts accompanying a program's source. Being able to combine the commands for the different targets into a single file, and being able to abstract out dependency tracking and archive handling, was an important step in the direction of modern build environments.


These says there are three common versions of make. BSD make, GNU make and Microsoft nmake. For the most part they are compatible as they all (mostly) conform to the POSIX standard. As I am a long time linux user, and linux's userland tools are GNU based I'll be using that version for this tutorial; however it should be possible to use any version of make without issue.

Advantages & Disadvantages
Make is a very useful tool to be sure, but it does have it's downsides. Makefiles themselves do not flow the same way many programming languages do, making it harder for some programmers to follow them. They also are difficult to debug as there are no debuggers for makefiles.

Some people criticize makefiles for large projects claiming that they do not scale very well; however there are entire operating systems (and kernels) which are built entirely using makefiles. For most uses makefiles are quite simple and work very well.

Makefiles are also much nicer to use then strait command line compilation when dealing with projects with more then a couple of source files. Setting up a makefile is usually pretty quick; and being able to type “make” is a whole lot simpler then typing out all the required gcc commands required to build a project.

What's a Makefile?
Makefiles are what make uses to determine what to build and how to build it. It could almost be considered a program of it's own; one that is interpreted by the Make utility. They consist of two things, variables and rules. Variables in makefiles can be set on the command line when calling make or in the makefile itself. While I refer to them as variables, variables in makefiles are a lot more like constants in programming lnaguages then actual variables. Rules tell make what depends on what, and how to build things.

Let's start with an example. In our example we have three code files and two headers: main.cpp, utilities.cpp and utilities.hpp, and graphics.cpp and graphics.hpp. Here's our makefile:
makefile:

# makefile
########################################
# build options
#
CXX_FLAGS = -Wall -g


########################################
# rules
#

app.exec : main.o utilities.o graphics.o
        g++ main.o utilities.o graphics.o -o app.exec
       
main.o : main.cpp utilities.hpp graphics.hpp
        g++ ${CXX_FLAGS} -c main.cpp -o main.o
       
utilities.o : utilities.cpp utilities.hpp
        g++ ${CXX_FLAGS} -c utilities.cpp -o utilities.o
       
graphics.o : graphics.cpp graphics.hpp
        g++ ${CXX_FLAGS} -c graphics.cpp -o graphics.o


#######
.PHONY : clean
clean :
        @touch app.exec
        @touch rm.o
        @rm -f *.o app.exec
       


"Ahh! My eyes!" I hear you exclaim, but it's not nearly as bad as it might seem at first. First, any line starting with # is a comment; comments are wonderful things. After the first 4 lines of comments we have a variable. CXX_FLAGS is any parameters we wish to pass to g++, in this case we're telling g++ to display all warnings and to include debugging info. Variables are pretty easy to declare, just <name>=<contents> on a line of it's own.

After our variable we have a few more comments to seperate variables from rules (for readability). Then the evil part: rules. Really they are not so evil, a little complex perhaps; but not evil. The first line has two parts the file to be created (built) followed by : followed by any dependencies. In our example app.exec depends on main.o, utilities.o and graphics.o. The next like tells make what to run to build app.exec. It's important that it is indented with a tab as that's how make knows that it's an action. Actions can also be multiple commands on multiple lines, so long as they are all prefaced with a tab. It's also important that app.exec actually gets generated somehow, as otherwise make might complain. The first rule is fairly simple; it's a pretty starndard g++ invocation.

The next rule is a little bit more interesting. Again we have a file to be built and it's dependencies. main.o depends on main.cpp and the two headers. It's pretty similar to the first rule in that regard; it's the action where the interesting stuff is. In this case we're calling g++ and passing the contents of our CXX_FLAGS variable as a parameter (${CXX_FLAGS} is replaced with -Wall -g) then we pass the file to be compiled and what to output. Again, standard g++ invocation except for the ${CXX_FLAGS} bit, but ${CXX_FLAGS} is our variable which will be replaced with it's contents when the command is actually run. The rules for utilities.o and graphics.o follow the same pattern as main.o.

There is one rule left but it's different then the others and I'll cover it in a bit. So just skip over it and don't let your mind explode just yet Wink


Phony rules
Makefiles are mostly used to simply building projects, but sometimes you need it to do other things as well. Say you want to delete all the intermediate object files (*.o) and the executable and recompile everything. Sure it's not hard to delete everything by hand but makefiles can be used to make it even easier!

Convention is to include a rule called "clean" in your makefiles. That way you can just call "make clean" in order to clean all the object files and the executable. The problem is that rules are supposed to make a file, which is hte opposite of what we are trying to accomplish. To this end the creators of make came up with an ingenious idea: phony rules.

A phony rule is one that doesn't create any files; or doesn't create the file named in the first line of the rule. In order to appease make and make sure it doesn't get confused you have to tell is that your rule is a PHONY rule. That's accomplished with
makefile:
.PHONY : clean
. Then we actually define the "clean" rule with
makefile:
clean :
You'll note that there are no dependencies for clean; that's because it doesn't depend on anything (duh!).

The clean rule is fairly interesting beyond being a phony rule however. Unlike our other rules it consists of multiple lines; and each line is prefaced with an @. Multiple line rules are actually pretty easy. Just make sure each line of the action is prefaced with a tab and make will consider it all one rule until it finds a line without a tab. The @ preface is also pretty simple: it just tells make not to ouput the command when it runs it. In this case it means that "make clean" will output nothing. Included in the clean rule is two touch commands, they are there simply to make sure that rm actually has something to remove.

And there we have it, a simple makefile! Not so scary eh? There are a few other important things to know about makefiles before you go writing your own: the first rule is run when you don't tell make what to do (this is how make is usually run) and make uses last modified times to tell when things need to be updated so don't mess with the times on your objects if you want make to be able to tell if they need to be rebuilt.

[TBD] Advanced makefiles, maybe automake


: