The basic operation of Make is to find the name of a needed target in the description, ensure that all of the files on which it depends exist and are up to date, and then create the target if it has not been modified since its generators were. The description file really defines the graph of dependencies; Make does a depth-first search of this graph to determine what work is really necessary.
Make also provides a simple macro substitution facility and the ability to encapsulate commands in a single file for convenient administration.
It is common practice to divide large programs into smaller, more manageable pieces. The pieces may require quite different treatments:
The code resulting from these transformations may then need to be loaded together with certain libraries under the control of special options.
Related maintenance activities involve running complicated test scripts and installing validated modules.
Forgetting to compile a routine that has been changed or that uses changed declarations will result in a program that will not work, and a bug that can be very hard to track down. On the other hand, recompiling everything in sight just to be safe is very wasteful.
The program described in this report mechanizes many of the activities of program development and maintenance. If the information on inter-file dependences and command sequences is stored in a file, the simple command make is frequently sufficient to update the interesting files, regardless of the number that have been edited since the last "make". In most cases, the description file is easy to write and changes infrequently. It is usually easier to type the make command than to issue even one of the needed operations, so the typical cycle of program development operations becomes
think -> edit -> make -> test . . .
Make is most useful for medium-sized programming projects; it does not solve the problems of maintaining multiple source versions or of describing huge programs. Make was designed for use on Unix.
Make does a depth-first search of the graph of dependences. The operation of the command depends on the ability to find the date and time that a file was last modified.
To illustrate, let us consider a simple example: A program named
progis made by compiling and loading three C-language files
lSlibrary. By convention, the output of the C compilations will be found in files named
z.oAssume that the files x.c and y.c share some declarations in a file named
defsbut that z.c does not. That is, x.c and y.c have the line
#include "defs"The following is a long-winded description file is equivalent to the one above, but takes no advantage of make 's innate knowledge:
prog : x.o y.o z.o
cc x.o y.o z.o -lS -o prog
x.o : x.c defs
cc -c x.c
y.o : y.c defs
cc -c y.c
z.o : z.c
cc -c z.cIf this information were stored in a file named Makefile , the UNIX command
makewould perform the operations needed to recreate prog after any changes had been made to any of the four source files x.c , y.c , z.c , or defs .
If none of the source or object files had changed since the last time prog was made, all of the files would be current, and the command
makewould just announce this fact and stop. If, however, the defs file had been edited, x.c and y.c (but not z.c ) would be recompiled, and then prog would be created from the new ".o" files. If only the file y.c had changed, only it would be recompiled, but it would still be necessary to reload prog .
If no target name is given on the make command line, the first target mentioned in the description is created; otherwise the specified targets are made. The command
make x.owould recompile x.o if x.c or defs had changed.
If the file exists after the commands are executed, its time of last modification is used in further decisions; otherwise the current time is used.
Make operates using three sources of information: a user-supplied description file (as above), file names and "last-modified" times from the file system, and built-in rules to bridge some of the gaps.
The following text describes the same relationships and operations as the previous Makefile but also uses some facts that make already assumes about C compilations:
prog : x.o y.o z.o
cc x.o y.o z.o -lS -o prog
x.o y.o : defs
In this shorter example, the first line says that prog depends on three ".o" files. Once these object files are current, the second line describes how to load them to create prog . The third line says that x.o and y.o depend on the file defs . From the file system, make discovers that there are three ".c" files corresponding to the needed ".o" files, and uses built-in information on how to generate an object from a source file (i.e., issue a "cc\ -c" command).
$(Z)The last two invocations are identical.
$$stands for a single dollar sign. All of these macros are assigned values during input, as shown below. The following fragment shows their use:
OBJECTS = x.o y.o z.o
LIBES = -lS
cc $(OBJECTS) $(LIBES) -o prog
. . .The command
makeloads the three object files with the lS library. The command
make "LIBES= -ll -lS"loads them with both the Lex ("-ll") and the Standard ("-lS") libraries, since macro definitions on the command line override definitions in the description. (It is necessary to quote arguments with embedded blanks in UNIX commands.)
Four special macros change values during the execution of the command:
$*, $@, $?, $<.They will be discussed later.
The following sections detail the form of description files and the command line, and discuss options and built-in rules in more detail.
. . . . . . . . . ( end of section Introduction) <<Contents | End>>
A description file (makefile) contains three types of information: macro definitions, dependency information, and executable commands.
There is also a comment convention: all characters after a sharp (#) are ignored, as is the sharp itself. Blank lines and lines beginning with a sharp are totally ignored. If a non-comment line is too long, it can be continued using a backslash. If the last character of a line is a backslash, the backslash, newline, and following blanks and tabs are replaced by a single blank.
A macro definition is a line containing an equal sign not preceded by a colon or a tab. The name (string of letters and digits) to the left of the equal sign (trailing blanks and tabs are stripped) is assigned the string of characters following the equal sign (leading blanks and tabs are stripped.) The following are valid macro definitions:
2 = xyz
abc = -ll -ly -lS
LIBES =The last definition assigns LIBES the null string. A macro that is never explicitly defined has the null string as value. Macro definitions may also appear on the make command line (see below).
Other lines give information about target files. The general form of an entry is:
target1 [target2 . . .] :[:] [dependent1 . . .] [; commands] [# . . .]
[(tab) commands] [# . . .]
. . .Items inside brackets may be omitted. Targets and dependents are strings of letters, digits, periods, and slashes. (Shell metacharacters "*" and "?" are expanded.) A command is any string of characters not including a sharp (except in quotes) or newline. Commands may appear either after a semicolon on a dependency line or on lines beginning with a tab immediately following a dependency line.
A dependency line may have either a single or a double colon. A target name may appear on more than one dependency line, but all of those lines must be of the same (single or double colon) type. [ Foot_note_1 ]
A built-in rule may also be executed. This detailed form is of particular value in updating archive-type files.
Make normally stops if any command signals an error by returning a non-zero error code. (Errors are ignored if the "-i" flags has been specified on the make command line, if the fake target name ".IGNORE" appears in the description file, or if the command string in the description file begins with a hyphen. Some commands return meaningless status).
Because each command line is passed to a separate invocation of the Shell, care must be taken with certain commands (e.g., cd and Shell control commands) that have meaning only within a single Shell process; the results are forgotten before the next line is executed.
Before issuing any command, certain macros are set.
If the command was generated by an implicit rule (see below),
If a file must be made but there are no explicit commands or relevant built-in rules, the commands associated with the name ".DEFAULT" are used. If there is no such name, make prints a message and stops.
make [ flags ] [ macro definitions ] [ targets ]The following summary of the operation of the command explains how these arguments are interpreted.
First, all macro definition arguments (arguments with embedded equal signs) are analyzed and the assignments made. Command-line macros override corresponding definitions found in the description files.
Next, the flag arguments are examined. The permissible flags are
-iIgnore error codes returned by invoked commands. This mode is entered if the fake target name ".IGNORE" appears in the description file.
-sSilent mode. Do not print command lines before executing. This mode is also entered if the fake target name ".SILENT" appears in the description file.
-rDo not use the built-in rules.
-nNo execute mode. Print commands, but do not execute them. Even lines beginning with an "@" sign are printed.
-tTouch the target files (causing them to be up to date) rather than issue the usual commands.
-qQuestion. The make command returns a zero or non-zero status code depending on whether the target file is or is not up to date.
-pPrint out the complete set of macro definitions and target descriptions
-dDebug mode. Print out detailed information on files and times examined.
-fDescription file name. The next argument is assumed to be the name of a description file. A file name of "-" denotes the standard input. If there are no "-f" arguments, the file named makefile or Makefile in the current directory is read.
Finally, the remaining arguments are assumed to be the names of targets to be made; they are done in left to right order.
If there are no such arguments, the first name in the description files that does not begin with a period is "made".
If there are no "-f" arguments, the file named makefile or Makefile in the current directory is read. The contents of the description files override the built-in rules if they are present.
If the file
x.owere needed and there were an
x.cin the description or directory, it would be compiled. If there were also an x.l, that grammar would be run through Lex before compiling the result. However, if there were no
x.cbut there were an x.l, make would discard the intermediate C-language file and use the direct link in the graph above.
It is possible to change the names of some of the compilers used in the default, or the flag arguments with which they are invoked by knowing the macro names used. The compiler names are the macros AS, CC, RC, EC, YACC, YACCR, YACCE, and LEX. The command
make CC=gccwill cause the "gcc" command to be used instead of the usual C compiler. The macros CFLAGS, RFLAGS, EFLAGS, YFLAGS, and LFLAGS may be set to cause these commands to be issued with optional flags. Thus,
make "CFLAGS=-O"causes the optimizing C compiler to be used.
. . . . . . . . . ( end of section Details) <<Contents | End>>
The code for make is spread over a number of C source files and a Yacc grammar. The description file contains:
# Description file for the Make command
P = und -3 | opr -r2 # send to GCOS to be printed
FILES = Makefile version.c defs main.c doname.c misc.c files.c dosys.c\
gram.y lex.c gcos.c
OBJECTS = version.o main.o doname.o misc.o files.o dosys.o gram.o
LINT = lint -p
CFLAGS = -O
cc $(CFLAGS) $(OBJECTS) $(LIBES) -o make
-rm *.o gram.c
@size make /usr/bin/make
cp make /usr/bin/make ; rm make
print: $(FILES) # print recently changed files
pr $? | $P
make -dp | grep -v TIME >1zap
/usr/bin/make -dp | grep -v TIME >2zap
diff 1zap 2zap
rm 1zap 2zap
lint : dosys.c doname.c files.c main.c misc.c version.c gram.c
$(LINT) dosys.c doname.c files.c main.c misc.c version.c gram.c
ar uv /sys/source/s2/make.a $(FILES)Make usually prints out each command before issuing it. The following output results from typing the simple command make in a directory containing only the source and description file:
cc -c version.c
cc -c main.c
cc -c doname.c
cc -c misc.c
cc -c files.c
cc -c dosys.c
mv y.tab.c gram.c
cc -c gram.c
cc version.o main.o doname.o misc.o files.o dosys.o gram.o -lS -o make
13188+3348+3044 = 19580b = 046174bAlthough none of the source files or grammars were mentioned by name in the description file, make found them using its suffix rules and issued the needed commands. The string of digits results from the "size make" command; the printing of the command line itself was suppressed by an @ sign.
The @ sign on the size command in the description file suppressed the printing of the command, so only the sizes are written.
The last few entries in the description file are useful maintenance sequences. The "print" entry prints only the files that have been changed since the last "make print" command. A zero-length file print is maintained to keep track of the time of the printing; the $? macro in the command line then picks up only the names of the files changed since print was touched. The printed output can be sent to a different printer or to a file by changing the definition of the P macro:
make print "P = lpr -sp"or
make print "P= cat >zap"
The most common difficulties arise from make 's specific meaning of dependency. If file x.c has a "#include "defs"" line, then the object file x.o depends on defs ; the source file x.c does not. (If defs is changed, it is not necessary to do anything to the file x.c, while it is necessary to recreate x.o)
To discover what make would do, the "-n" option is very useful. The command
make -norders make to print out the commands it would issue without actually taking the time to execute them.
If a change to a file is absolutely certain to be benign (e.g., adding a new definition to an include file), the "-t" (touch) option can save a lot of time: instead of issuing a large number of superfluous recompilations, make updates the modification times on the affected file. Thus, the command
make -ts("touch silently") causes the relevant files to appear up to date. Obvious care is necessary, since this mode of operation subverts the intention of make and destroys all memory of the previous relationships.
The debugging flag ("-d") causes make to print out a very detailed description of what it is doing, including the file times. The output is verbose, and recommended only as a last resort.
I would like to thank S. C. Johnson for suggesting this approach to program maintenance control. I would like to thank S. C. Johnson and H. Gajewska for being the prime guinea pigs during development of make.
(Lesk): M. E. Lesk, "Lex \(em A Lexical Analyzer Generator", Computing Science Technical Report #39, October 1975.
The make program itself does not know what file name suffixes are interesting or how to transform a file with one suffix into a file with another suffix. This information is stored in an internal table that has the form of a description file. If the "-r" flag is used, this table is not used.
The list of suffixes is actually the dependency list for the name ".SUFFIXES"; make looks for a file with any of the suffixes on the list. If such a file exists, and if there is a transformation rule for that combination, make acts as described earlier. The transformation rule names are the concatenation of the two suffixes. The name of the rule to transform a ".r" file to a ".o" file is thus ".r.o". If the rule is present and no explicit command sequence has been given in the user's description files, the command sequence for the rule ".r.o" is used. If a command is generated by using one of these suffixing rules, the macro $* is given the value of the stem (everything but the suffix) of the name of the file to be made, and the macro $< is the name of the dependent that caused the action.
The order of the suffix list is significant, since it is scanned from left to right, and the first name that is formed that has both a file and a rule associated with it is used. If new names are to be appended, the user can just add an entry for ".SUFFIXES" in his own description file; the dependents will be added to the usual list. A ".SUFFIXES" line without any dependents deletes the current list. (It is necessary to clear the current list if the order of names is to be changed).
The following is an excerpt from the default rules file:
.SUFFIXES : .o .c .e .r .f .y .yr .ye .l .s
$(CC) $(CFLAGS) -c $<
.e.o .r.o .f.o :
$(EC) $(RFLAGS) $(EFLAGS) $(FFLAGS) -c $<
$(AS) -o $@ $<
$(YACC) $(YFLAGS) $<
$(CC) $(CFLAGS) -c y.tab.c
mv y.tab.o $@
$(YACC) $(YFLAGS) $<
mv y.tab.c $@
. . . . . . . . . ( end of section Make - A Program for Maintaining Computer Programs) <<Contents | End>>