Introduction to the Make utility

Goals

By the end of this lab, you should:

Introduction

The make utility is a powerful tool that can be used to manage the compilation of computer programs. Although the programs that you have written up to this point can probably be compiled with one line on the command prompt, as your computer programs grow you will want to be able to automate the compilation process so that you can concentrate on the program you're writing, and not on how to make it compile.

1. The Compiler

The make utility issues commands to a compiler in order to translate the files that you have written in C++ into machine language that the computer can execute. The compiler that we will use is called GCC which stands for the GNU Compiler Collection. Even though we will use it to compile C++ programs, it can also be used to compile programs in C, Objective-C, Java, Fortran, and Ada. To use the C++ compiler from the collection, we use the program “g++”. To find out what version of the GCC compiler your machine is using type the following command:

g++ -v

Near the end of this page we'll take a look at a typical g++ command and review what every part of the command means.

2. The Makefile

The way that you communicate with the make utility is by writing a “Makefile”. The make utility looks for this file when it is envoked from the command line. The file must be named either “Makefile” or “makefile”. In the makefile you will write some comments and declare some variables. You will also specify some rules which will consist of a target, prerequisites, and some commands. In the remainder of this section we will look at how to specify each of these.

2.1 Comments

As with everything in Computer Science, documenting what you are doing is absolutely imperative. Just as you comment your code in C++, you should also comment what program your makefile is used for and what each rule in your makefile does. To write a comment in a Makefile you begin the line with a # sign.

# This is a comment in a makefile.
# You should use them often.

2.2 Variables

In previous courses you've probably been told ( and hopefully experienced ) the usefullness of variables. Just like C++, make allows you to use variables in you makefile, although the syntax is a little bit different than that of C++. First of all, variables in a makefile don't have a type ( like int, float, etc ). All variables are basically strings, and the string that the variable represents is substituted for the variable when the make utility interprets your makefile. If I wanted a variable to contain the name of my .cpp file, I might write the following line in my makefile:

MYFILE=main.cpp

A Second difference between C++ variables and variables in a makefile is in the way that the variable is referenced, or used. If you want to use a variable that you have declared, you type a $ sign and then the name of the variable inside of parenthesis. So, If I wanted to use the variable that I declared above I would type:

$(MYFILE)

Some common variables used in makefiles are the following:

2.3 Rules

After you have defined the variables that you want to use, the next section of the makefile is the definition of rules. You can think of rules as a set of operations that the make utility will perform depending on which one you tell it to. The general form of a rule is:

target: prerequisites ...
commands
...
...

2.3.1 Targets

The target is the name that you give a rule. For instance, if I wanted to compile everything in my project, I would have a target named “all”. Another common target name is “clean”, people use this target to get rid of any unneeded files, such as the executable, or backup files when they are cleaning up a directory. This is a handy target to have when you are cleaning things up before you turn in a project.

2.3.2 Prerequisites

Prerequisites are a space separated list of things that must exist or be done before before any of the commands in the rule are executed. The items in the list of prerequisites can be file names, or the targets of other rules. So if I had three targets named: put_on_shoes, put_on_socks, and walk_to_school; I would probably want to put the target names: put_on_socks and put_on_shoes in the prerequisites list for walk_to_school. It might look something like this:

walk_to_school: put_on_socks put_on_shoes
commands
...
...

2.3.3 Commands

Each line containing a command must start with a tab character. You can't use spaces to get it spaced over far enough either! It must be a tab!
Now that the whole tab thing is crystal clear; what is a command? A command is any line that you can type at the linux command prompt. You could use the commands: cd, ls, mkdir, rmdir, rm, etc; however most of these commands won't accomplish much. The most common command that is used is “g++”.

3. Using g++

The general format of any command in linux is typically:

command option(s) file(s)

g++ uses the same general format. Let's assume that we have written a program in a file called “my_prog.cpp”. The program is a word processor, so we want the executable to reflect that. No one will know it's a word processor if we just call it “a.out”. Let's decide to have the compiler create an executable named “Writer”. Also, we want the compiler to leave debugging information in the executable, give warnings about anything that it doesn't think is good codeing style, treat warnings as errors, and make sure that our code complies with the ANSI C++ standard. Let's take a look at the command line and then see what each part does:

g++ -g -Wall -W -Werror -pedantic -ansi -o Writer my_prog.cpp

Each piece of the command line can be interpretted as follows:

g++
This is the name of the compiler program.
-g
Leave debugging information in the executeable. This allows a programmer to use a debugging tool to step through the program code one line at a time and watch how variables change. This quarter we will learn how to use a debugging tool.
-Wall
The -Wall option tells the compiler, “If I do anything in this program that is really odd, warn me.” C++ has a long long list of things that could possibly indicate a programming mistake, but by default many of them are not checked during compilation. Adding -Wall ensures that many of the more complex things it can warn you about will be reported. ( It is like having a very picky set of eyes checking over your program for you. )
-W
Not all warnings are turned on by the -Wall option. Yeah, it seems kinda stupid, but that's the way it is. The -W option tells the compiler, “I'd like you to warn me about all of the simple mistakes that people might make. ” This lets g++ do some of the checking for you that would otherwise cause you lots of time and trouble. ( Remember, warnings that are caught are often logic errors that you don't have to fight! This is a very good thing. )
-Werror
The -Werror option is related to the other options in that they all deal with warnings. Unlike the others, it doesn't enable additional warning messages. Instead, -Werror tells the compiler, “If you ever feel like warning me about anything, consider that an error and do not compile any further.” This way even if you wanted to ignore the warnings, you would have to find a way to convince the compiler that you really know what you are doing.
-pedantic
The -pedantic option tells the compiler to use only standard features of the language and not to allow any non-standard C++ features that the compiler may allow.
-ansi
The -ansi option tells the compiler to only allow ANSI complient C++.
-o Writer
Write the output the the file “Writer”. If you do not use the -o option, the output will be written to a file named “a.out”.
my_prog.cpp
This is the file to be compiled.

4. Writing a Makefile

Now we have all the tools that we need in order to write a makefile that we can use to compile our word processor program. ( Or any other program for that matter ) Our makefile might look something like this:

# This is a makefile for the Writer word processor. To
# generate the executeable, type “make all” or
# “make” at the command prompt. To remove the
# executeable and any extranious files type
# “make clean” at the command prompt.

# Here are some variables to make our lives easier once we
# start writing larger makefiles
CXX=g++
CXXFLAGS=-g -Wall -Werror -pedantic -ansi

# The all target compiles the whole project and creates
# an executable called “Writer”
all: my_prog.cpp
$(CXX) $(CXXFLAGS) -o Writer my_prog.cpp

# The clean target removes the executable and any extranious
# files
clean:
rm -f *~ Writer

Create a directory called “intro_make” and within that directory save the above text into a file called “Makefile”.

5. Using the Makefile

Now that we have a makefile, it's time to put it to use. Break out your favorite editor and bust out the “Hello World!” program and save it in the same directory that you saved the makefile with the name “my_prog.cpp”. It's not exactly a word processing program, but it will do for now. The following code should work fine:

#include<iostream>

// This is the “Hello World!” program
int main()
{
using std::cout;
using std::endl;

cout << "Hello World!" << endl;
}

Now let's use make to compile our program. The way to use make is to type “make” followed by the target name of the rule that you want executed. So, if you want to make everything you should type:

make all

If you want to clean up unnecessary files you should type:

make clean

If you don't specify a target, make uses the first rule that it finds in the makefile. For this reason, many programmers write the rule which compiles everything as the first rule and then only type “make” at the command line. After you have used the make utilty to compile you program, run the executable “Writer” and make sure that it works. Then type “make clean” and see if your directory is cleaned up.

6. Compiling for the Big C++ Graphics Package

This section is a bit long winded, and for that I apologize. However, its fairly easy reading and will definitely help you understand what all that junk on the command line to g++ is all about. Its worth it to take the time to actually read this section rather than skipping to the end.

Compiling and writing code for use with the graphics package will be a bit different than you're used to, though not really any more difficult. When writing code for the graphics package you must sharp include "ccc_win.h". The first major difference that you will encounter is that you aren't really writing the main function. You simply write a function that the main calls. The function that you write must be named "ccc_win_main()" and return an integer type. Even though you are only writing a function, and not the main, this is only a shift in paradigms and has absolutely no effect on how the code is written. For all intensive purposes you are really writing the main and just using a different name.

If you were working on your own machine, you would go to the books web site, download all of the code for the graphics package, and put it somewhere on your hard drive. Things are a bit different here at school because you only have so much space in your account. Because of this limitation, and the logistics of getting everyone to download everything and agree where to put it, systems has downloaded a single copy of the files and made them available to everyone. All we need to do is figure out how to include those files when we compile our programs.

This might be a bit boring, but what we are going to do necessitates a short discussion about compilers and how things are compiled. Some programs are very large and take a very long time to compile. One of these programs is called "X11". It is the code that allows you to draw things on the screen, and the program that the author of the text chose to use for the graphics package. Obviously none of us want to sit around for a few hours every time we want to compile one of our simple programs. To avoid this, the code for "X11" has been compiled into a library. A library is a bunch of code that has already been compiled. If you use a function from a library in your code, all the complier has to do is look up where to find the code for that function in the library's index, and include that part in your code. This makes your program compile much faster.

Now that you know that you need to include the X11 library, all you need to know is how to tell g++ that your are going to use that library. The two things that you need to tell g++ are what libraries you plan on using, and where it can find those libraries. The way you tell g++ to use a library is by using the "-l" option followed directly by the library name. No space goes between the -l and the name. To tell the compiler that we want to use the X11 library we would write "-lX11" in the command to g++. To tell g++ where to find the libraries we use the "-L" option.( This one is OK with a space ) Since the X11 library can be found in /usr/X11R6/lib, we would write "-L /usr/X11R6/lib" in the command to g++.

The code for the Big C++ graphics package has also been compliled into a library. The name of the library is "ccc" and it can be found in "/usr/local/lib"

So, assuming that we have a file called "ccc_main.cpp" that contains the function "int ccc_win_main()" as well as any other code that we have written, we would compile it with the following line:

g++ -Wall -W -Werror -pedantic -ansi -L /usr/X11R6/lib -L /usr/local/lib -o a.out ccc_main.cpp -lX11 -lccc

Its really that simple.( Note the sarcasm ) This is the reason that we are learning how to use a makefile. How things get compiled can become a little bit tricky, and its nothing that we want to have to type more than once. So lets put this into a makefile:

# Variables make your life easy

# The compiler and compiler options
CXX=g++
CXXFLAGS= -Wall -W -Werror -pedantic -ansi

# libraries to use
LIBS= -lX11 -lccc

# where to look for libraries
LIBDIRS= -L /usr/X11R6/lib -L /usr/local/lib

# compile a Big C++ Graphics Package program
all:
$(CXX) $(CXXFLAGS) $(LIBDIRS) -o a.out ccc_main.cpp $(LIBS)

# remove unnecessary files
clean:
rm -rf *~ a.out
  

7. Where to get more information

This introduction barely scratches the surface of the capabilities of the make utility. Throughout the quarter we will be learning more about how to use the make utility, but that information too will just be a drop in the bucket of the many things you can do with make. If you'd like to take a look at the documentation of the make utility, check out the make manual.