Makefiles .... Through Several Levels

Learning to use make is a gradual process, and so I have broken it down into several stages ... or "levels" .... so that you can gradually understand how to automate compiling and linking as you work with increasingly complex situations. You may not feel like an expert by the end of the course, but I hope that you will at least be able to create your own Makefile that can handle several files needed to create one largish program (i.e. Level 3) by the time you take the final.

Although it is technically not part of C, being able to create and use Makefiles is essential for working with large, multi-file programs written not only in C, but C++ as well. (And other languages such as Ruby follow similar patterns .... it's worth your while to try to understand this.)

We give you the generic - and rather intimidating - Makefile at the beginning of this class so that you can create interesting programs quickly. Don't worry that you don't understand that file right away. Work through this reading (which has a few exercises in it too) in stages.

Overview

As you saw in an early lab on C programming, compiling and linking files to create an executable program can involve long, complex commands that are difficult to type correctly repeatedly. Even if you cut and paste the commands, you can easily run into errors if you miss a letter at the beginning or end of what you are copying. And this is with a fairly simple and small (trust me on this.....) program. Can you imagine trying to do this process if you are working with a large team that is working on a program that requires dozens, if not hundreds, of files? It would be a nightmare just trying to remember the names of the files and which one(s) you have been modifying most recently.

In order to cope with this complexity, we automate the process of compiling and linking C programs together using the make command.

Make is a command given to the computer that tells it to create an executable program. This command generally relies upon a set of instructions that are stored in a textfile that MUST be named Makefile. The name must be exact, and it must be capitalized. It should also be in the same directory as the files of the program that you are trying to compile.

(If you are saying "wait a minute ...... you don't HAVE to ....." at this point, jump down to Level 5 or take a look at the GNU Make manual page.)

Level 0: "Compiling by Hand"

Really, there is no magic to using the make command. There are some short cuts that save us time and effort, but in essence make is a way to automate running the same commands you could do by hand every time you need to compile your program.

For this gradual build up, let's go back to the time-honored tradition of writing a first program that does nothing besides print "Hello, world!" and name it something like hello.c just to make it easier to follow through the various levels together. Don't forget that you will need to include the stdio.h file in order to print.

Once you have created and saved your file, try to compile and run it. Don't use make! If necessary, go back to step 3 of the Elementary C Programming ... lab to see what the command line format should be.

Level 1: Simple Makefile

To follow along with this set of instructions, you need to create a new directory that does NOT currently have a Makefile in it. We are going to create our own, simple Makefile, and we cannot have two different files with the same name in a directory.

  1. Make a new directory and move your hello.c file into it.
  2. Don't forget to change directory to the new one before you start the next steps!!
  3. Create a new Makefile that has only two lines:

    hello: hello.c
         clang -o hello hello.c

Note that before "clang" in the second line, there should be a tab. You cannot use multiple spaces to achieve the same appearance. It must be a tab! This is a signal to make that this indented line contains the actual commands needed to create the file specified at the beginning of the first line.

In the first line, "hello:" is called the target of the command. This is what file will be created. In this simple case, it is the final executable file. But if you are using multiple files to build a large, complex final program, these targets might be intermediate files with names such as hello.o. The rest of that line contains a list of files that need to be located in that directory in order to do the compiling. This list is called the "dependencies" for the command.

The second line contains the command that will actually create the target file given in the first line. This is often called "the recipe" because it contains the directions for making the target.

All Makefiles will be composed of sets of these two types of lines.

So now, you have the simplest Makefile that can be created. Use it to compile your hello program by issuing the command:

make hello

If there are no typos, you should see the second line echoed as make runs. If you get an error, it is usually because a filename has been misspelled OR you didn't use a tab to indent the second line (or, of course, you could have a problem within your sources files too ....).

If there are no errors, then your program compiled and can be run.

So, how is this level different from level 0?? The answer is that there is not much difference yet, other than the fact that typing make hello is shorter (and less error-prone) than typing clang -o hello hello.c. This will become much more helpful as we have more files and more complex commands to automate.

Level 2: Makefile with Linking

When we use header files such as stdio.h or math.h, the preprocessor actually makes a copy of the code from the header file in our program. The header file tells the compiler about new functions we can call in our code, but it doesn't include the function's implementation. During compilation, we must explicitly link to the corresponding library, which is where the implementation of the function resides. Without the library, the compiler will see a call to a function such as printf and not know what it is.

Helpfully, compilers such as clang automatically link to some libraries, for example the library that contains functions declared in stdio.h is automatically linked when we use clang. Functions declared in math.h are in a separate library, so we must include the flag -lm to link this library. For example: clang -o quadratic quadratic.c -lm. This line could be run from the command line or included in a makefile as above.

The name of a library you pass in with the -l flag often does not match the name of the header file you include, so you will need to refer to the documentation for a function (e.g. man pow) to find out which header file and library you need to include to use the function in your program.

Level 3: Makefile with Multiple Files

You should start this level of reading and experimentation while working on the Program Management reading and lab (about Week 10). If that reading and lab is making sense to you, congratulations! You can skip this section and jump down to Level 4. If dividing up the code and creating a Makefile to compile the resulting files is not making sense yet, try working through this section.

Makefiles really begin to be extremely useful when you start to work on a project with multiple files. Compiling with make not only automates the process of compiling and linking multiple files, but it will keep track of what files have been modified (and hence need to be recompiled) and those that have not (so that we can save time by not recompiling them).

The order in which files appear in a Makefile can be important; so you may need to edit the file a few times before everything goes smoothly. But once that Makefile is written, it will save a lot of time.

Read (or re-read) King Section 15.4 for more details on how a multi-file program is compiled and linked ... and some of the debugging you may need to do.

If you are having touble understanding how to construct a Makefile to compile a program from multiple files, try experimenting with a set of two or three very simple files that do not do anything more than print statements to the screen to let you know that functions in all of the files are working. You know how to write quite sophisticated code already, but the point of this exercise is to understand how the files work together - so you are encouraged to keep it simple.

For example, go back to the directory you used for Level 2 and the hello program. Since this already has your main() function in it, we'll consider it the main file for the whole project and add one or two other files with functions that hello.c will call.

So, create another file that will print something easy. You can have it ask for and print the name of the user. In which case, we might want to call it something obvious such as name.c. Recall from what you have learned in the Program Management reading and lab, that you will also need to have a corresponding header (.h) file that will contain the function prototypes for any functions you write in the source (.c) file. Make sure that you call the new function from within hello.c so that you can see the result of your hard work.

The header file needs to be included in both the main file (in this case, hello.c) as well as in the source file that has the new functions (name.c) so that these programs know what functions exist, what parameters they expect, and what value (if any) they will return.

Once you have a couple of files ready, it is time to modify the Makefile you have in that directory. Using King section 15.4 as a guide, see if you can create two rules to compile name.c and hello.c .... and then to link them together to create the executable file hello. Remember that the rule to do the linking appears above the rules to do the compiling. And also that we are using clang as our compiler (although using gcc will not make a difference for this exercise - just be consistent.)

Did your result look like this example (which is NOT named Makefile so that you don't overwrite yours by accident)?

Level 4: Makefile Using Macros and Automatic Variables

C programmers hate to type any more than we have to. This comes from the early days of programming when typing a program was tedious and error-prone, and when computer memory was extremely limited. Hence, we tend to live by the adage "don't repeat yourself" and use whatever shortcuts are available to avoid typing and retyping. Which leads to the use of Makefile macros and variables.

Note that you can live a productive life as a C programmer without ever using Makefile's automatic variables. But, once you have mastered creating Level 3, Level 4 is simply a matter of defining macros and then substituting them in recipes as well as making use of the automatic variables. Mastering this level will make you a more efficient C programmer.

(Re)read the section on make and Makefiles in the reading on Program Management.

Then, take the Makefile from Level 3 and see if you can create macros and substitute them and automatic variables in the Makefile used to create the hello program. Try compiling. You may need to make a modification in a file in order for make to recognize that it needs to rebuild the program.

Level 5: Implicit Rules and Other Tricks

Ok, make has been around a long time, and over the decades, it has gotten very flexible and tolerant of variations. You can break a lot of the rules given above, in certain circumstances on certain operating systems. The program will use implicit rules and make some assumptions, saving programmers a fair amount of typing when the compilation target and rules can be deduced ... mostly based on consistent naming and common patterns.

If you give the command

make foo

and there is a file named foo.c in your current directory, make will assume that you want to compile foo.c into the object file named foo.o, which will be linked to make the executable foo. If there are multiple files that you want to compile and link into an executable .... or file names don't match, then you need a Makefile to define the steps explicitly.

This flexibility allows us to use a generic Makefile to compile and link programs, and if you look closely at this file, you will notice that there are no filenames specified! The implict rules will find the .c file that corresponds to the filename given in the command, giving us maximum flexibility to use this Makefile without having to modify it for every single project.

By the way, if you are compiling a single source file (and not linking it to anything else), you can even do without a Makefile entirely, so long as the specified executable name matches the name of the source file (minus the .c)!!

Readings and References

See King, section 15.4, pages 366 - 371 for an overview of using make and a Makefile for compiling and linking a program composed of multiple source and header files.

The GNU make manual works the reader through a progressing from an introduction to a complex example, explaining conventions and error messages along the way.