Bob’s simple makefile for the unix version of robfit makefile. Note that for ease in viewing the file extension is txt. In normal usage, there would be no file extension.
The make facility is one of UNIX’s most useful tools. It is essentially a programing language for automating large compilations. When used properly, make significantly reduces the amount of time spent compiling programs because it eliminates many needless compilations. Using make properly also guarantees that programs will be compiled with the correct options and linked to the current version of program modules and libraries.
The idea behind make is that you need not recompile a source file if a current object file already exists. An object file is current if it was compiled more recntly than the last change to the source file. For example, consider the follow situation: We have just modified program.F and want to compile and link it with the modules inputs.F and outputs.F.
The command
%
f77 program.F inputs.F outputs.F
compiles and links all three modules correctly. If the object modules inputs.o and outputs.o exist, and if they are current (i.e., the source files have not been changed since the object modules were last compiled), we could compile program.F with the command:
%
f77 proqram.F inputs.o outputs.o
In this case, f77 will compile program.F alone and link it to the two object files. Because this command makes a single compilation instead of three, it will take much less time. However, this compilation places more demands on the programmer, who must remember (or check) when input.F and output.F were last compiled and determine whether their object modules are current. This introdces potential for confusion: while compiling program.F and linking it to pre-existing object modules is faster, it only saves work if the object modules are correct.
make automates this process, reducing the potential for incorrect compilations while minimizing the overall amount of compilation needed. It determines whether the relevant object files exist and whether they are current and then performs the smallest set of compilations needed to create die output file you want. To understand how make performs this task, we need to define some terms:
target A task that needs to be performed. In many cases, a target will be the name of the file you want to create; often it will be a name assigned to a task (i.e., a target may be a filename, but it does not have to be).
Dependency A relationship between two targets: target A depends on target B if a change in target B produces a change in target A. For example, an object ifie buzz.c depends upon its source file.buzz.f and on any other file that is included within its source file during preprocessing (buzz.h). A change in any of these sources changes the contents of the object file when it is compiled.
up to date A file which is more recent than any of the files on which it depends. For example, if buzz.o is more recent than buzz.f and buzz.h it is up to date because it reflects the most recent changes in its source files. If buzz.o is older than buzz.f , then the source code has been modified since the last compilation, and buzz.o does not include the latest changes.
Makefile A file describing how to create one or more targets. It lists files on which the targets depend and gives the rules needed to compile these targets correctly. For most applications, you will want a single makefile in each source directory, describing how to compile the code in that directoiy. By default, make looks first for a makefile named makefile, then it looks for Makefile.
make A method of makeing a target means executing the commands from the makefile that desciibe how to create the target, plus any commands needed to bring the target’s dependencies up to date. A makefile may list many targets; you can make any of them with a single UNIX command.
To use make, you need to create a makefile describing how to create a file corectly then you need to enter the make command to execute the makefile. First, we describe how to create a makefile, which is by far the more difficult of the two topics. Second, we describe how to invoke make.
Any makefile. no matter how complex, is a set of instructions describing how to build a number of targets. Often the target is some file; however, a target can be any name describing some task. For example, the following code is a very simple makefile describing how to build production and debugging versions of stimulate:
A very simple makefile
# Lines beginning with # are comments
# Targets begin at the left margin, followed by :
# Shell command lines must begin with a tab.
stimulate:
# One or more commands to create stimulate
f77 -o stimulate -O stlmulate.F input.F output.F
Stimulate.db:
# One or more commands to create stimulate.db
f77 -DDEBUG -g -O stimulate.db stimulate.F \
input.F output.F
This makefile does nothing more than list the commands required to create the two different versions of stimulate. It says that the command:
% f77 -o
stimulate -O stimulate.F input.F
output.F
is sufficient to compile stimulate correctly, that optimization is in effect, and that no additional debugging code is included. Similarly:
%f77 -DDEBUG -g -o stimulate.db stimulate.F \
input.F output.F
will correctly compile the debugging version, stimulate.db. The symbol DEBUG is defined for the preprocessor, telling it to include any debugging code present in the F’ORTRAN source files. Optimization is disabled, and the compiler will generate the augmented symbol table needed for dbx debugging. However, this makefile does not use most of make’s features. In particular, this makefile does not eliminate extra compilations, but it executes the simplest (and slowest) command that will perform a correct compilation.
The first character on a command line must be the tab character (CTRL-I). A command line may not begin with a space or a series of spaces. This is perhaps the most common error in makefiles; it is difficult to find because the makefile usually appears correct. If it occurs, the make command will display the message “Must be a separator online a.” Some compilers and linkers are squeamish about object modules whose name does not end in .o. We assume that your system accepts these names.
make generates a new shell for each UNIX command line that it executes. Consequently, commands that are executed directly by the shell may only be effective on a single UNIX command line. In particular, the cd command only affects the command line on which it appears.
For example, consider the following lines:
cd . . / stimsource
f77 stimsource.F
The cd command is useless here; it is only effective on the line in which it appears. To change to the directory stimsource before compiling stimsourceF, use the command:
cd . . /stimsource; f77 stimsource.F
in your makefile. Remember that you can use the continuation character (\) to extend a single UNIX command line over several physical lines. If you do this, the \ must not be followed by any further characters, including spaces and tabs.
In general, to invoke make, enter:
% make target
where target is the name of one of the targets defined in the niakefile. If you omit target, make generates the first target. For example, our simple makefile can be invoked in three ways:
% make stimulate
or:
% make stimulate.db
or:
% make
The first command executes the commands needed to make the target stimulate, the second generates stimulate.db,, and the third makes the first target listed in the
makefile (which happens to be stimulate).
The previous makefile lists only the commands that will build the targets correctly and makes no attempt to minimize the amount of work it needs to do. Dependencies add the idea of conditional execution to a makefile. In the introduction, we said that a target buzz.o depends on a file buzz.f, a change in buzz.f results in a change to buzz.o. Using this idea, let’s analyze the compilation needed to produce the target stimulate from the previous makefile.
Note that stimulate depends on the source files stimulate F input.F, and output.F. However, this fact is not particularly useful—by itself, it will not let us write an efficient makefile. stimulate really depends on the object files stimulate.o, input.o, and output.o. We could even say that stimulate does not depend on stimulate.F at all. A change to stimulate.F has no direct effect on stimulate; changing stimulate.F affects the object file, stimulate.o, which in turn affects stimuate. The f 77 command hides this dependency by automatically running the UNIX linker; you must be aware of it when writing makefiles. To use these dependencies, we need to rewrite the compilation command to separate compilation and linking:
stimulate: stimulate.o input.o output.o
f77 -o stimulate stimulate.o input.o output.o
The first line says that the target stimulate depends on the object files stimulate.o, input.o, and output.o. The second line uses the f77 command to link these files, assuming that they already exist. Together, these two lines say “The target stimulate is out of date if it is older than stimulate.o, input .o, or output.o. If this is the case, create a new version of stimulate by linking these object files.” We use f77 to do the linking, rather than invoking the linker ld directly, even though no compilation is taking place. We could write two similar lines to correctly link the, debugging version, simulate.db. In general, a target line looks like this:
target-name: list-of-dependencies
This line says that target-name depends on all the files listed in the list-of-dependencies.
However, this does not account for compiling. The object files sthnulate.o, input.o, and output.o have their own dependency relationships: the corresponding source files and any other files (e.g. header files that might be included within the source files). Therefore, if siimulate.o is older than stimulate.F, we need to compile a new object file before linking everything to create the target stimulate. The same logic applies to input.o and output.o. To express these secondary dependencies in a makefile, we list all three object files as targets, with their own dependencies, and provide a command to compile an optimized version of each object file:
stimulate: stimulate.o input.o output.o
f77 -o stimulate stimulate.o input.o output.o
stimulate.o: stimulate.F
f77 -c -o stimulate.F
input.o: input.F headerfile.F
f77 -c -o input.F
output o: output. F
f77 -c -o output.F
In addition to the simple dependency relationship between the object file input .o and its source file, we have also stated that input.o depends on another file, headerfile.F. This could be a file providing some important definitions that the INCLUDE statement inserts into input.F during compilation. Obviously, a change to either headerfile.F or input.F will require input.o to be compiled anew. Remember to include such files as dependencies in your makefiles.
The previous example illustrates a crucial feature of makefiles: any target can depend on a file that is a target in its own right. In this case, make proceeds recursively by makeing the files on which the target depends, guaranteeing that they are up to date, before you makeing the original target. make will take the following steps to generate a target:
1. Check to see if the files on which a target depends are up to date with respect to their own sources.
2. Create a new version of any file that is out of date.
3. Check to see if the target is up to date with respect to the files on which it depends.
4. Create a new version of the target if it is out of date.
Because make is recursive, it continues checking dependencies until it can guar tee that all the files needed to generate a target are current. For example, if we
change the file oulput.F and use this makefile to generate a new version of the target stimuatee, make will take these steps:
1. Determines whether stimulate.o, input.o, and output.o are themselves targets.
2. Upon discovering that all three are targets, determines whether input.o. etc.
are up to date relative to their sources.
3. Determines that output .o is older than output if, and compiles a new version.
4. Checks stimulate’s date relative to stimulate.o, inputo, and the new verston of output.o.
5. Determines that stimulate is now out of date with respect to output.o.
6. Creates a new version of stimulate by linking the object files.
By definition, a target is out of date if it does not exist as a file. Therefore, a target which is a task name, rather than a filename, is always out of date. The command make task will always execute the task.
We can easily create a similar set of lines describing how to build the debugging version of stimulate, stimulate.db. These would be identical, except they would specify some different command line options for the compilation. These lines should be included with the preceding example in the same makefile; one makefile should describe all useful ways of compiling a given project. There is no restriction on the number of targets that can be in a makefile, and you can enter a UNIX command to make any target listed in the makefile. For example, the following lines compile stimulate,db with the -DDEBUG and -g options i.e., define the name DEBUG for the preprocessor and augment the symbol table for dhx. This is shown in the following example:
stjmulate.db: stimiulate.do input.do output.do
f77 -o stimulate.db stimulate.do input.do output.do
stimulate.do: simulate.F
f77 -o stimulate.do -c -DDEBUG -g stimulate.F
input.do: input.F headerfile.F
f77 -o input.do -c -DDEBUG -q input.F
outout.do: output.F
f77 -o output.do -c -DDEBIJG -g output.F
This generates a separate set of object files for debugging versions of stimulate.o,
Input.o, and output.o. presumably, these files have been compiled with additional debugging code which the preprocessor has included through conditional compilation.
Here is another feature we might want in the makefile: a special target to delete all of the object modules and start with a clean slate. In other words, when we enter the command make clean, we want to delete everything. To write this section of the makefile, we remember target names that aren’t filenames are always out of date. When we enter the command make clean, make looks for a file named clean. If a file doesn’t exist (and we’ll agree: we’ll never put a file with this name in our development directory), it will execute all of the commands listed for this target. Here is some typical code:
clean:
rm *.o *.do stimulate stimulate.db
Targets such as these are very common. It is also common to see a target named install, which moves the finished executable to its final resting place and sets its access modes appropriately.
These are the basics you need to write useful makefiles. Abbreviations, macros, and default compilation rules (discussed in the next section) are advanced features to let you write shorter, simpler makefiles. At this point you should be able to write a correct makefile for a complex development project. make can eliminate many needless compilations while guaranteeing your programs are compiled correctly. It performs bookkeeping you would otherwise do yourself. If you use make properly, you will not have to worry about when your object files were compiled or when the source code was modified. Once you have described a target’s dependencies accurately, make automates this task for you.
To simplify writing commands, a makefile can define substitution macros and use several predefined abbreviations. Here are two useful abbreviations:
$@ Stands for the full name of the target.
$* Stands for the name of the target with the suffix deleted.
To shorten the previous makefile, use the $@ and $* abbreviations as follows:
stimulate: stimulate.o input output.o
f77 -o $@ $*.o input.o output.o
stimulate.db: stimulate.do input.do output.do
f77 -o $@ $*.do input.o output.o
input.o: input.F inputdefs.H
f77 -c -o $*.F
input.do: . . .etc...
When compiling the target simulate, $@ and $* both stand for the string “stimulate”. When compiling the target input .o, $* stands for the string “input” and $@ stands for the string “input.o”. These abbreviations are useful in many situations; they are particularly important for writing default compilation rules (discussed below).
A macro definition begins at the left margin of the makefile and has the form:
macro-name = macro-body
When make is processing the makefile, it substitutes macro-body for the string
$(macro-name). Therefore, we can shorten the preceding makefile even more by
defining the macro abbreviations DEPENDS and DBDEPENDS (i.e., dependencies
for the production and debugging versions) as follows:
DEPENDS = input.o output.o stirnulateo
DBDEPENDS = input.do output.do stimulate.do
stimulate: $(DEPENDS)
f77 -o $@ $ (DEPENDS)
stimulate.db: $ (DBDEPENDS)
f77 -o $@ $ (DBDEPENDS)
Macros are also useful for defining a set of compilation options. For example, the
macro:
F66FLAGS = -onetrip -66 -w –o
is a collection of options for FORTRAN 66 compatibility. The makefile line:
F77 -c $ (F66FLAGS) input.F
would therefore compile input.F in FORTRAN 66 compatibility mode with optimization. Remember that make requires a macro invocation to appear between the delimiters $(and).
Normally, make looks for files in the current directory. Adding a VPATH line to the makefile allows make to find files in directories other than the current directory. A VPATH line has the following form:
VPATH=dir1:dir2: ... :dirn
That is, VPATH should be set equal to a list of directories, which are separated by colons. The previous line means that when make looks for a file, it will look first in the current directory, then in dir1 then in dir2, and soon until it has exhausted the directory list. This feature is useful if the source code for a large program is Split among several directories or if many different executable programs use the same source code. In the latter case, the source code can be in a single directory, the object files and makefile for each executable program can be in a separate directory, and each makefile can use a VPATH line to point to the source code directory.
Note
VPATH does not work properly in some older versions of make.
Makefiles can become even more efficient if you use default rules to define how to build targets. With default rules, you do not need to specify how to build a target explicitly. Instead, you define some standard actions that make will use in most common situations.
To use this feature, you must define a set of significant suffixes. (Standard UNIX documentation refers to these suffixes as prerequisites. We consider this term obscure and do not use 1t however, you should be aware of it) The appearance of these suffixes in a target line will cause make to use a default rule to generate this target, if an appropriate rule exists. To specify the list of significant suffixes, use the SUFFIXES: keyword, followed by the list of suffixes that will be involved in any default rules. By itself, SUFFIXES clears the suffix list. When it is followed by one or more suffixes, a SUFFIXES: line adds the new suffixes to the list.
For example, the lines:
# Start by clearing the list of suffixes.
SUFFIXES:
# We want to specify detault rules for .F, o. and .do files.
.SUFFIXES: .F .o .do
declare that any default rules in this makefile will involve the suffixes F, o, and do.
To specify a default rule, list the suffixes that uniquely determine when the rule is applicable. The last suffix in the list must be the suffix of the target. Follow this list by a colon and a semicolon, then enter a UNIX command, stated in as general terms as possible. There must be no spaces in the suffix list for a default rule. For example, the statement
.F.o:; f77 –c –o $@ -0 $*.F
supplies a compilation rule that will be used by default whenever a target line states that a .o file depends on a .F file. When this is the case, make will use the FORTRAN compiler to compile the file named target. F with optimization. producing the object file tarqet. O —provided, of course, that the makeflie does not supply compilation insuuctions explicitly.
Similarly. the statement
.F.do:; f77 –c -o $@ $(DEBUGFLAGS) $*.F
specifies a default rule for compiling debugging versions of object flies. This compilation rule will be used when a target line lists a F file as a dependency for a i file and when no explicit compilation commands appear.
Putting this in context, consider the following makefile:
# start by clearing the list 0 suffixes.
.SUFFIXES:
#We want to specify default rules for .F, o, and .do files
.SUFFXXES: .F .o .do
DEBUGFLAGS = -DdEBUG -g
PRODUCTFLAGS = -w –o
PRODUCTOJS = program.o other.o
DEBUG0BJS = program.do other.do
EXECNAME = product
#A default rule to make a “production” .o file.
.F.o:’ f77 –c –o $@ $ (PRODUCTFLAGS) $*.F
# A default rule to make a .do file (debugging .o module)
.F.do:; -c -o @ $ (DEBUGFLAGS) $*.F
#Generate executables:
production: $ (PRODUCTOBJS)
f77 -o $ (EXECNAME) $ (PRODUCTOBJS)
debug: (DEBUGOBJS)
F77 -o $(EXECNAME) $ (DEBUGOBJS)
#Target line to make some object modules.
program.o: program.F header.H
otber.o: other.F
* Target lines to make some debugging object modules,
program.do: program.F header.H
other.do: other.F
clean:
rm *.o *.do
rm product
This is a complete makefile. The comnand make program.o checks the date of program.F and, if needed, uses the default rule for creating a .o file. This default
rule leads to the compilation:
%f77 -c program.o -w -O program.F
Which is compilation with optimization and with warning messages suppressed.
Similarly, the command make otber.o invokes the compilation:
%f77 -c other.o -w -O other.F
The commands make other.do and make program.do create debugging versions of these programs, as determined by the preprocessor variable DEBUG, with no optimization and debugging capabilities. The commands make production and make debug generate production and debugging executable files by linking (and if necessary, compiling) the appropriate object files. Finally, make clean deletes all object modules and executables. Note that the default rules for building .o and .do files work because they use abbreviations heavily. Abbreviations let you write generic compilation commands that can be used for many different compilations.
The make facility can take this a step further and provide its own default rules for compilation. For example, if you do not provide a default rule for FORTRAN compilation, make will supply its own default rule. Consequently, the following line is a complete makefile:
Program.o: program.F
With this makefile, the command make executes the compilation:
% f77 -c program.F
make has its own default rules for FORTRAN, C, lex, yacc. and some other languages. In the extreme, a null makefile (i.e., a makefile that is 0 bytes long) is perfectly valid. With a null makefile, the command:
%make program.o
executes the command:
%f77 -c program.F
This is supplied completely by the make’s default rules. However, this is not a particularly good way to use make, because these internal default rules do not. and cannot, know anything about the options you want to use for compilation. Consequently, using them sacrifices much of the versatility that UNIX compilers offer.
To use the make facility, create a file named makefile in the directory where the source code on which you are working resides. After you have written the makefile, enter the command:
%make target-name
within this directory, where target-name is the name of the task you want to per form. In many cases, it will be the name of a file you wish to generate. Often, it will simply be the name of a task and will not correspond to any file. With the make command, make reads the makefile in the current directory and executes whatever commands are needed to create the target you named successfully. If you omit target-name, make will create the first target described in the makefile.
Normally, there is a single makefile per directory. This makefile should govern all the compilations for the source code within the directory; of course, it may link this code to other modules that reside elsewhere. Ideally, you and your colleagues have coordinated software development in a way that lets you link your to standard versions of other modules that have been released.
This section lists the most useful options for make. Others are
described in the UNIX Programmer’s Reference Manual
-f filename
Normally, the make command looks for a makefile within the current directory that is named either makefile or Makefile. This option tells make to use filename as a makefile, rather than the standard makefile. For example, the command:
%make -f otbermake target
looks in a file named othermake for instructions describing how to make target.
-n
Don’t execute any commands. Instead, list the commands that would be executed under normal conditions. In other words, make lists the commands it would issue if it were to make a target now, without taking any action. This is useful for debugging. Remember that make’s behavior is time-dependent since make’s behavior depends on the last time a file was modified, it will not necessarily take the same actions whenever you use it.
-i
Normally, make terminates immediately if a command returns a nonzero error code. When used with make, some UNIX commands return this error code incorrectly. The -i option forces make to ignore these error codes. (Equivalently, the makefile can include the special entry .IGNORE.) This is particularly useful because some UNIX programs return a nonzero exit code incorrectly when used with make.
-k
The-k option is similar to -i, except that make will not attempt to build the target in which the error occurs. It will continue to build any other targets required.
-q
Don’t execute any commands. Instead, determine whether the target of this make is up to date. Return a zero status code if the target is up to date, and
return a nonzero status code if the target is not.
-s
Normally, make prints all commands that it executes on the terminal. With this flag, make is silent and does not print any messages. (Equivalendy, the makefile can include the special entiy SILENT.)
-t
Don’t execute any commands found within the make file, Instead, “touch” all target files that are out of date, making them Up to date without changing them.
Bob – I am skipping this part.
Both the standard UNIX documentation and this chapter have described make in terms of automating compilations. It can also be used for maintaining documentation, accounting, or any other task that involves a large number of files, make has been used to tailor on-line documentation to a system configuration by running editor scripts to include and delete sections from manuals.
Remember that makefiles are much more than command lists. They are programs with complicated rules for conditional execution, default actions, and other features. In short, make is one of UNIX’s most versatile tools when used appropriately. The time you can save during compilation is worth the time you invest in writing your makefile.
The formal Open Watcom
Make command line syntax is shown below.
WMAKE [options] [macro_defs] [targets]
As indicated by the square brackets [ ], all
items are optional.
options
is a list of valid Open Watcom
Make options, each preceded by a slash ("/") or a dash
("-"). Options may be
specified in any order. E.g.
/a make all targets by ignoring time-stamps
/d debug mode - echo all work as it progresses
/f myfile causes Make to
read “myfile” instead of the default
"MAKEFILE".
/m do not search for MAKEINIT file
/n no execute mode - print commands without
executing
/u UNIX compatibility mode
macro_defs
is a list of valid Open Watcom
Make macro definitions. Macro
definitions are of the form:
A=B
and are readily identified by the presence of the
"=" (the "#" character may be used instead of the
"=" character if necessary).
Surround the definition with quotes (") if it contains blanks
(e.g., "debug_opt=debug all"). The macro definitions specified on the
command line supersede any macro definitions defined in makefiles.
targets
is one or more targets described in the makefile.
The programming example involves four FORTRAN 77 source files and two include files.
#
# programming example
#
plot.exe : main.obj input.obj calc.obj output.obj
wlink @plot
main.obj : main.for defs.fi globals.fi
wfc386 main /mf /d1 /warn
calc.obj : calc.for defs.fi globals.fi
wfc386 calc /mf /d1 /warn
input.obj : input.for defs.fi globals.fi
wfc386 input /mf /d1 /warn
output.obj : output.for defs.fi globals.fi
wfc386 output /mf /d1 /warn
[1] Mike Loukides, UNIX for FORTRAN Programmers, O’ Reilly and Associates, Inc, 103 Morris Street, Sebastopol, CA 95472, (1990, 1991) pp 149-165
I paraphrase a bit, but this is “essentially” from his book.