How-to: Using make and makefiles

©2000 by Wayne Pollock, Tampa Florida USA.
All Rights Reserved.

  1. What is make?

    Make is a program based on a Unix system utility over 25 years old.  It is mainly used to maintain programs based on many source files.  The instructions on how to build a program and what source files a program depends on are specified in a makefile.  Make can be used for other tasks as well.

  2. What is a makefile?

    A makefile is a list of what needs to be built for a project.  A project may include several executables and/or DLLs called targets.  For each target a makefile may specify what source, object, and library files are needed to build the target.  These are called dependents.  It is entirely possible (even common) that a dependent for one target is itself a target.

    For each target and its dependents, the makefile may also specify the exact sequence of commands needed to build the target.  Below is a simple makefile that shows how to build hello.exe from hello.cpp (where hello.cpp is a complete program in a single file):

    
    hello.exe: hello.cpp
            bcc32 -ehello.exe hello.cpp

    You can test out make by typing the above into a file called makefile and also put hello.cpp in the same directory such as C:\Temp.  (Note white space is significant in a makefile.  The first line starts in column one, and the second has a leading tab.)  This example assumes your C++ compiler is called bcc32, which is the case if you are using the free Inprise/Borland C++ 5.5 compiler for Windows.  If you are using some other C++ compiler, modify the second line appropriately for your compiler and system.  (For a Unix/Linux system the second line should probably read "g++ -o hello.exe hello.cpp".)  Now type make as shown below and see what happens.

    In reality you would not need a makefile for such a simple example.  However even if the project only has one or two source files a makefile can sometimes be useful.  The executable may need to be compiled with certain options, or using some non-standard library (such as the XML C++ parser library).  Help files (man pages for Unix/Linux) and configuration files may need to be installed in various directories, or registry keys may need to be set.

  3. How is make used?

    Once the makefile is written, all you need ever type to rebuild the project is the command:

    
    C:\Temp> make 

    The make command looks for a file in the current directory called "makefile" or "Makefile" and builds the first target listed in the file.  (It is possible to call your makefile something else, see make options below.)  If the makefile contains several targets, you can specify which one you'd like to build on the command line.  Thus the previous example is exactly equivalent to:

    
    C:\Temp> make hello.exe 

    Make is smart in that it only builds things if it needs to.  If hello.exe exists and is newer than all of its dependents, then nothing will be done when running make.  If only some of the dependents are newer, only they will get re-compiled.  You can appreciate this feature when you consider that some commercial software projects consist of over 2000 source files and only 25 of them may have changed since the last time the target was built.  Even on modern PCs it can take a lot of time to compile 2000 source files!  Make will keep track of which object files are out of date with respect to their source files and only re-compile those.

    Another nice feature of make is that it comes configured for many common situations.  To build foo.exe from foo.cpp, no makefile is needed!  Just enter the command "make foo.exe" and make will realize there is no makefile, will look for source files named foo that it knows how to build into executables, will find foo.cpp, and then will build it.

    A complete working (but simple) makefile for a voicemail project is shown below.  If this example makes sense to you, you can skip the remaining sections of this document, which describe make and makefiles in more detail.

    
    # Simple Borland makefile for voicemail project.
    # (Note the system knows how to build an obj file from a cpp file.)
    # (C)2000 by Wayne Pollock, Tampa Florida.  All Rights Reserved.
    
    # The .exe depends on all the object files.
    vmail.exe: VMail.obj MBox.obj Msg.obj Telephone.obj Main.obj
    	bcc32 -evmail.exe VMail.obj MBox.obj Msg.obj Telephone.obj Main.obj 
    
    clean:
    	-DEL *.obj  # If the files don't exist this produces an error.
    	-DEL *.tds  # The leading "-" says ignore any error & continue. 
    

  4. What are some common make command-line options?

    Make can be used with many options specified on the command line.  For example entering the command "make -n target" won't actually build anything.  Instead this option causes make to print out the commands it would run (had you not specified the -n option) when building target.  This is useful for determining which files and directories make will use when installing something.

    To name your makefile something interesting such as foo.mak, you can use the "-f" option ( i.e., "make -f foo.mak") to tell make which makefile to use.

    To display a brief summary of options for make, enter "make -h".

  5. How common are make and makefiles?

    Makefiles are at the heart of all project files today.  For instance, Borland projects, Symantic Cafe projects, and MS Visual Studio projects simply provide GUI interfaces for a project's makefile, although sometimes extra information is kept in a project file as well.  In fact most of these systems have a menu command to write out the makefile which you can then easily look at with any text editor (such as NotePad).  Unix, Linux, Windows, and Macintosh systems today usually come with a stand-alone make utility already.  (If your system does not you can easily find and download one from the Internet.  Borland C++ v5.5 comes with make (including the free downloadable version used at HCC).  The GNU project also contains a popular free make program as well as additional developer tools.

  6. How portable are makefiles?

    There are many versions of make floating around these days, including the original make, Gnu's gmake, imake, nmake (MS provides this with Windows), and others.  Fortunately the basic makefile syntax is the same for all of them.  However there are some differences, so you should read the documentation on the version of make you are using before creating complex makefiles.  The makefile syntax discussed here should be common to nearly all versions of make.

    A different problem is that the commands listed in a makefile to build a target are dependent on your system and compiler.  For example, the commands used on Windows with a Borland compiler will not work well on a Linux system using g++ (the GNU C++ compiler).  Make does provide a feature called macros that help isolate the system dependencies but it is still a problem.  (See configure).

  7. What is the syntax of makefiles?

    A makefile is a plain text file containing entries.  In addition to entries a makefile may contain blank lines and comments.  All entries (except for rules) must fit on a single line, however a line ending in a backslash ("\") continues onto the next line.  (The backslash and newline are replaced with a space.)  In some places in an entry white space is significant.  Furthermore all entries must start in column one.

    There are four types of entries allowed in a makefile:  macro definitions, make directives, implicit rules, and explicit rules.  Both types of rules may be (and usually are) multi-line entries.  All lines except the first must be indented by at least one tab.  (A single tab is common; some versions of make permit one or more spaces instead.)  The first line of a rule is known as the dependency line and the remaining lines (if any) are known as the command lines.

    These are all described in the table below but first a reminder:  Most makefiles are simple and quite short, containing some comments at the top and one explicit rule (or just a few).  Make contains some powerful defaults so you don't usually need to write more than this.  However for the adventurous out there I will try to describe some of the other features that may be found in a makefile.  This is not intended to replace your make documentation!  Borland's make comes with extensive tool help, and does the other make programs such as Gnu make.

    To keep the discussion interesting I have provided a sample makefile.  This makefile is not typical, but instead tries to show every make feature in the context of a realistic example, with lots of comments.  (In real life the original makefile for voice mail project was less than a dozen lines long, comments and all.  However the sample makefile used here will work fine, at least with Borland make.)

    Blank lines Blank lines may appear almost anywhere to improve the readability of the makefile.  However, rules in a makefile may be multi-line entries, and inserting a blank line in the middle of one would confuse make into thinking you had two broken entries rather than a single rule.
    Comments Any line containing a # contains a comment.  All characters from the # to the end of the line (including the newline) are ignored.  Not all versions of make allow you to put a comment on every line.  However Borland make does, even on lines containing DOS commands.  Comments have no effect on make, they are there as an aid to the (human) reader of the makefile.
    Macros Make allows you to define macros, which are short names that expand out to an arbitrary string of text.  Like all entries, macro definitions start in column one and contain the macro's name, an equal sign, and the replacement text (up to the end of the line).  Macros may be defined in terms of other previously defined macros.  (Macros may also defined on the command line to make.)  Some example macro definitions are:

    
    OBJS = foo.obj bar.obj \ 
    	boz.obj
    CFLAGS = -A -O
    CC = bcc32.exe 
    

    The first macro definition defines the macro OBJS to the list of three object files (foo.obj, bar.obj, and boz.obj).  Notice the use of the backslash to continue the line.

    The second macro defines some options to pass to the C++ compiler when building object files.  Should the options change (e.g., add a debug option), you need only change a single line and all the compile commands later in the makefile would use the new set of options.

    The last macro defines CC to be the name of the C++ compiler to use.  (This and many other macros are defined by default by most versions of make.)

    A macro may be used anywhere in a makefile after it has been defined.  To use a macro, type its name in parenthsis and preceeded by a dollar sign.  So $(OBJS), $(CFLAGS), and $(CC) can be used anywhere and will expand out to their definitions.

    Using some macros, the simple makefile shown previously becomes:

    
    # Simple makefile with macros. 
    
    OBJS = VMail.obj MBox.obj Msg.obj Telephone.obj Main.obj 
    DEL = DELETE  # DOS delete command
    
    vmail.exe: $(OBJS)
    	$(CC) -evmail.exe $(OBJS)
    
    clean:
    	-$(DEL) $(OBJS)
    	-$(DEL) *.tds
    

    Make also defines some special macros that are defined automatically.  These are a single character each.  (When using them the parenthesis are optional.)  These macros have meaning only in certain places within your makefile.  In fact the same macro had different meanings depending on where it is used; make redefines them as needed.

    There are considerable differences in special macros between various make versions, so care is needed: read your local documentation!  GNU make is available for all platforms and contains a large (and very useful) set, including macros for object files within a library when the library is the target, and other goodies.  However, the following two special macros are fairly standard:

    $@ This macro is valid in rules only, and expands to the full pathname of the target of the rule.
    $? This macro is valid in rules only, and expands to to list of out of date dependents of the rule.

    Here's a Linux example that uses both macros.  There are several interesting points about it:  I have modified the example to create and use a DLL.  (Note how simple it is to do under Linux.)  The example also shows an advanced macro feature, substitution.  It often happens that you define a macro for object files, then need to reference the source files.  Or vice versa.  This example shows how to do that.

    
    # Makefile showing special macros and substitution. 
    
    OBJS = VMail.o MBox.o Msg.o Telephone.o 
    
    vmail: Main.o
    	$(CC) -o $@ $? -lvmail
    
    libvmail.so: $(OBJS)
    	$(CC) -shared -o $@ $(OBJS)
    
    Main.o: Main.cpp $(OBJS:.o=.h)
    	$(CC) -c $(@:.o=.cpp)
    
    .cpp.o:
    	$(CC) -c -fpic $?
    

    If this appears cryptic, it's probably because it is.  On Linux (and Unix), "-lXYZ" means to use a library named "libXYZ.so".  The last entry redefines the rule for building object files; "-c -fpic" are the options that cause the compiler (probably g++) to generate object files suitable for including in a DLL.  Since the new rule is no longer correct for compiling Main.cpp, an explicit rule for Main.o was added.  Notice how Main.o needs to be recompiled if the source code or any header file has changed.  With macros and substitution, this is easy to do.  (Once you get used to it!)

    Notice how easy it would be to add another source file to this project; only the OBJ macro needs to be appended!

    One final note about macros: if you wish to use a dollar sign in your filenames or (more commonly) in commands, you must double up the dollar sign:

    
    cost: foo$$bar.cpp
    	echo "It costs $$29.95 to run this program!" 
    

    Directives yada yada yada goes here.
    Rules

    yada yada yada goes here.

    One advanced feature is the use of the ".cpp.obj" rule (which is also pre-defined in in the make configuration file (for Borland the file is called "BUILTINS.MAK", and found in the same directory as make.exe).  This rule says that a file foo.obj depends on the file foo.cpp, and provides the commands to build the .obj files.  An alternative is to have many rule entries like:

        foo.obj: foo.cpp
                $(CC) $(CPPFLAGS) $(INCLUDE_PATH) -c $*.cpp
    




Send comments and mail to the author.