.! .! MAKE version 1.1, April 1987 .! Copyright (C) 1987 by Jesse Perry. .! MAKE is in the public domain and may be freely distributed, .! used, and modified, provided this notice is not removed. .! .! make.rno .! This file is a tutorial introduction to MAKE. The HELP entry for .! MAKE (MAKE.RNH) refers to this file. It says that the user can .! print a file called MAKE$DOCUMENT for more information. This file .! should therefore be RUNOFF'd and the logical name MAKE$DOCUMENT set .! to be full pathname of the .MEM file. .! .nnm .ps 58 .lm 5 .rm 75 .sp 1 .spr 4 .fl bo .be .nfl space .b 3 .c ^*MAKE TUTORIAL\* .b 1 .ap .c ^*Introduction\* Large programs typically have more than one source file. Each source file is compiled separately. The resulting object files are then linked together to produce an executable image. A major advantage of this organization is that when changes are made, only the modified source files need to be re-compiled. When making changes, however, it is easy to forget which source files have been modified. The MAKE program checks the modification date of each file to determine which files must be re-compiled. (The modification date is by default the file creation date. The file revision date is used if the /REVISED qualifier is given.) MAKE divides files into two classes; ^*targets\* and ^*prerequisites\*. A target file is a file which is produced by some DCL command. A prerequisite file for a given target file is a file used as input by the command which produces the target file. For example, if you have a fortran file called test.for, you can compile it with the FORTRAN command to get an object file, test.obj. Then test.obj is the target file, and test.for is its prerequisite file, or equivalently, test.obj ^*depends on\* test.for. Notice that a file can be both a target ^*and\* a prerequisite. Test.obj is a target file with prerequisite file test.for, but it is itself a prerequisite file for target test.exe. Also, a target may have more than one prerequisite file. For example, if test.for includes test.inc, then both test.for and test.inc are prerequisites of test.obj. (Notice, test.inc is ^*not\* a prerequisite of test.for.) .b 1 .c ^*Dependency Statements\* MAKE reads a ^*makefile\*, whose name you specify in the logical name MAKE$DEFNAME, or on the command line using the /MAKEFILE qualifier. The makefile tells MAKE what target files you want kept up to date, and what prerequisite files each target file depends on. The makefile contains ^*dependency statements\* which describe what files depend on what other files, and give the commands needed to update each file when it is out of date with respect to any of its prerequisite files. A target file is ^*out of date\* with respect to a prerequisite file if the target file does not exist, or if its modification date is ^*earlier\* than the modification date of the prerequisite file. The following is a sample makefile which shows how to specify the example given above, using MAKE. (Throughout this document, the sample makefiles will be set off from ordinary text by "#-----"). .b 1 .nf #----- test.obj : test.for fortran test.for #----- .f This makefile contains a single ^*dependency statement\*. The statement says that file test.obj depends on file test.for, and that if test.obj is out of date with respect to test.for, the command "fortran test.for" should be executed. The file test.obj is the ^*target\* of the dependency statement. Test.for is the only prerequisite of test.obj. A dependency statement must have at least one name in the target list (that is, before the ':'). It may have any number of names in the prerequisite list (after the ':'), and any number of command lines following the initial target/prerequisite line. The target name in a dependency statement must not be indented. The command line(s) which follow the target line must each start with at least one space or tab. This leading white space tells MAKE that the line contains a command. Suppose that in the above example, test.for contains an include statement to import a file named stuff.inc. Then test.obj must depend on both test.for and stuff.inc, since if either file is modified, test.obj must be re-compiled to reflect the changes. A makefile expressing these dependencies is, .b 1 .nf #----- test.obj : test.for stuff.inc fortran test.for #----- .f .bl 1 Notice that test.obj depends on stuff.inc; test.for ^*does not\*. If test.for and subs.for are the source files for a program called test.exe, then the makefile to keep test.exe up to date is, .b 1 .nf #----- test.exe: test.obj subs.obj link test.obj, subs.obj test.obj: test.for stuff.inc fortran test.for subs.obj: subs.for fortran subs.for #----- .f Comments in a makefile can help explain what is being done. A MAKE comment starts with a '#' and stops at the end of the line. The above makefile, with some comments, is, .b 1 .nf #----- # Test.exe is produced from modules test.obj and subs.obj. test.exe: test.obj subs.obj link test.obj, subs.obj # link names .exe after first file # Test.obj depends on stuff.inc because test.for includes it. test.obj: test.for stuff.inc fortran test.for # Subs.obj depends only on subs.for. subs.obj: subs.for fortran subs.for #----- .f MAKE does not, by default, update every target in the makefile. It updates only the targets which are specified on the command line. If no targets are named on the command line, it updates the ^*first\* target that it finds in the makefile. If MAKE is run with no arguments, using the example makefile just given, it will update test.exe. Since test.exe depends on test.obj and subs.obj, MAKE must update these targets before it can update test.exe. So in this case, MAKE does update all the targets in the makefile, but only because the first target in the makefile depends on all the others. Assume there is also a file called test.rno containing documentation for the program test.exe. The runoff program converts a .rno file into a .mem file. Thus, test.mem depends on test.rno. This dependency can be added to the makefile, so that MAKE will keep both the program and its documentation file up to date. The makefile to do this is, .b 1 .nf #----- test.exe: test.obj subs.obj link test.obj, subs.obj test.obj: test.for stuff.inc fortran test.for subs.obj: subs.for fortran subs.for test.mem: test.rno runoff test.rno #----- .f .b 1 Using this makefile, the command "$ MAKE" will update test.exe, while the command "$ MAKE TEST.MEM" will update test.mem. Any number of targets can be named on the command line, separated by commas. For example, the command to update both test.exe and test.mem is, "$ MAKE TEST.EXE, TEST.MEM". .b 1 .c ^*MAKE Macros\* Let's consider a different example. Three files named first.for, second.for, and third.for are the source files for a program called lab5.exe. No include files are used. The makefile to maintain lab5.exe is, .b 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=lab5.exe first.obj, second.obj, third.obj first.obj: first.for fortran first.for second.obj: second.for fortran second.for third.obj: third.for fortran third.for #----- .f .b 1 This makefile contains quite a bit of repetitive information. Make macros can be used to reduce this repetition. MAKE translates two kinds of macros. ^*Named macros\* allow arbitrary strings of text to be placed wherever they are needed. ^*Dependency macros\* provide a notation to refer to the parts of a dependency statement. Four dependency macros are automatically defined within each dependency statement. Each is identified by a single punctuation character. The name of the current target is given by the dependency macro '@'. The file-name of the current target is given by the dependency macro '%' (i.e. without device, directory, or type). The name of the current target with its file type (e.g., .for, .obj, etc.) deleted is given by the dependency macro '_*'. The list of prerequisites of the current target is given by '?'. Each dependency macro is invoked by a '$' followed by the single character name of the macro. The first dependency statement in the above makefile is, .nf .b 1 #----- lab5.exe: first.obj second.obj third.obj link /exe=lab5.exe first.obj, second.obj, third.obj #----- .b 1 .f The target of this statement is lab5.exe. Throughout the rest of the statement, the dependency macro '@' is defined as 'lab5.exe', and the dependency macro '_*' is defined as just 'lab5' (because the .exe is deleted for the '_*' macro). The dependency macro '@' can be used to rewrite the command line of this statement: .b 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ first.obj, second.obj, third.obj #----- .f .b 1 When MAKE processes the command line, it will replace the '$@' with 'lab5.exe'. Similarly, the other dependency statements in the makefile can be re-written using the '_*' macro to produce, .b 1 .nf #----- # This makefile is exactly equivalent to the # first makefile given for this example. # Within this statement, '$@' is defined to be 'lab5.exe'. lab5.exe: first.obj second.obj third.obj link /exe=$@ first.obj, second.obj, third.obj # Within this statement, '$_*' is defined to be 'first'. # So MAKE translates '$_*.for' into 'first.for'. first.obj: $_*.for fortran $_*.for # Within this statement, '$_*' is defined to be 'second'. # So MAKE translates '$_*.for' into 'second.for'. second.obj: $_*.for fortran $_*.for # Within this statement, '$_*' is defined to be 'third'. # So MAKE translates '$_*.for' into 'third.for'. third.obj: $_*.for fortran $_*.for #----- .f Of course, $_* and $@ ^*cannot be used in the target part of a dependency statement\*, since the target part of the statement is what ^*defines\* these macros. For example, both dependency statements in the following makefile are invalid: .b 1 .nf #----- $_*.obj: foo1.for fortran foo1.for $@: foo2.for fortran foo2.for #----- .f The example makefile can be further simplified using the '?' macro. As stated above, this macro is defined as the list of prerequisites of the dependency statement it is used in. The example makefile, with $? used in all dependencies except the first, is, .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ first.obj, second.obj, third.obj first.obj: $_*.for fortran $? second.obj: $_*.for fortran $? third.obj: $_*.for fortran $? #----- .f The '?' macro is a bit harder to use in the first dependency statement, because the arguments to link must be separated by commas. If we just replace the link arguments with '$?', we get, .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ $? #----- .f .bl 1 MAKE will translate this dependency statement into, .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ first.obj second.obj third.obj #----- .f .bl 1 This link command line will fail, because the arguments are not separated by commas. The next idea is to append a comma to the use of the '?' macro, just as '.for' was appended to the use of the '_*' macro. That is, change the dependency statement to, .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ $?, #----- .f .bl 1 MAKE will expand this as, .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ first.obj, second.obj, third.obj, #----- .f .bl 1 This is almost right -- notice that the comma was appended to ^*each\* name in the prerequisite list. Unfortunately, the comma appended to the last filename will cause another link error. To get around this, if any single character is placed ^*between\* the '$' and the macro symbol (in this case '?'), that character will be used as a ^*separator\* when MAKE expands the macro. The most common separators are ',' and '+'. Using this feature, we can re-write the makefile as, .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ $,? first.obj: $_*.for fortran $? second.obj: $_*.for fortran $? third.obj: $_*.for fortran $? #----- .f .bl 1 MAKE will expand this makefile into precisely the original makefile. This makefile uses all three dependency macros; '_*', '@', and '?'. Nonetheless, it still has a noticeable amount of repetition. The last three dependency statements are identical, except for the names of their targets. As mentioned briefly above, MAKE allows a dependency statement to have more than one name in its target list. When MAKE finds such a statement in a makefile, it creates a copy of the statement for each name in the target list. Each copy has only one name in its target list, but is otherwise identical to the original statement -- each has the same dependency list and command lines. We can take advantage of this to compress the example makefile still further: .bl 1 .nf #----- lab5.exe: first.obj second.obj third.obj link /exe=$@ $,? first.obj second.obj third.obj: $_*.for fortran $? #----- .f .bl 1 MAKE will expand the second dependency statement into three separate statements, each with one target. It will then expand the macros in each target to produce the original example makefile. MAKE also allows user-defined macros called ^*named macros\*. A named macro is defined by a line containing the macro name, followed by an equal sign ('='), followed by the text for the definition. Once a named macro has been defined, it can be used like a dependency macro. Instead of a single punctuation character, a named macro is referred to by its name, in parentheses, after the '$'. We can reduce the example makefile further by defining a named macro for the various modules of the lab5.exe program: .bl 1 .nf #----- LABMODULES = first.obj second.obj third.obj lab5.exe: $(LABMODULES) link /exe=$@ $,? $(LABMODULES): $_*.for fortran $? #----- .f .bl 1 This makefile can be easily updated if a new module (say, fourth.obj) is added to lab5.exe. Only the definition of LABMODULES must change. Everything else will be taken care of automatically by the macro and target expansion which MAKE performs. MAKE expands each use of a named macro in the same way it expands each use of a dependency macro. Text attached to the use of the macro (e.g., the '.for' in '$_*.for') is attached to each name in the macro definition after the expansion. Also, a separator character can be inserted after the '$' and before the '('. For example, the following makefile is exactly equivalent to the one just given: .bl 1 .nf #----- LABMODULES = first second third lab5.exe: $(LABMODULES).obj link /exe=$@ $,(LABMODULES).obj $(LABMODULES).obj: $_*.for fortran $? #----- .f .bl 1 The trickiest part of this makefile is the use of LABMODULES in the link command line, '$,(LABMODULES).obj'. MAKE expands this macro in two steps. First, it creates the list of names in the expansion by copying the attached text ('.obj') after each name in the definition of LABMODULES. Then it appends the separator character ',' to every name except the last in the expansion list. .bl 1 .c ^*Default Rules\* In the example makefile above, the second dependency statement tells MAKE how to create each object file from its fortran source file. This is such a simple thing that it is inconvenient to have to specify it at all -- if we need to create an object file from ^*any\* fortran file, we must use the fortran compiler to do it. MAKE can be told this by a ^*default rule\*. A default rule tells MAKE how to create one particular kind of file from another kind. In this case, we need a rule which specifies how to create a .obj file from a .for file. The default rule for this is, .bl 1 .nf #----- _.for.obj: fortran $_*.for #----- .f The default rule statement has a syntax similar to a dependency statement. Its "target" is just the source file type followed by the target file type. Unlike a dependency statement, every default rule statement must include at least one command line -- otherwise, the default rule is useless. Ordinarily, no prerequisites are named in the default rule. MAKE knows that the actual target (in this case, the .obj file) depends on the source (.for) file, so it automatically makes the source file a prerequisite of the target file. Since MAKE can infer what the target and prerequisite files are, all of the dependency macros are defined within each default rule statement. In the example above, for instance, '$_*.for' is ^*not\* expanded into '.for.for', as one might expect (since the target is '.for.obj', and therefore the target without its .obj file type is '.for'). Instead, when MAKE needs to update an object file which is not a target of any dependency statement, and a fortran file with the same name exists, MAKE will automatically create a dependency statement whose target is the object file, and whose only prerequisite is the fortran file. The command line of the default rule, 'fortran $_*.for' will become the only command line of this new dependency. MAKE will then use the dependency statement just like any other to update the object file. Other prerequisites of a default rule can be specified after the ':'. For example, if every fortran source file includes a file called stuff.inc, the following default rule can be used. .bl 1 .nf #----- _.for.obj: stuff.inc fortran $_*.for #----- .f A number of commonly used default rules are provided in the default rules file. MAKE reads this file before reading the makefile. The default rules file name is given in the logical name MAKE$DEFRULE. If a rule is defined in the default rules file, and re-defined in the makefile, the makefile definition will be used. If a rule is defined several times, only the last definition which MAKE reads will be used. .bl 1 .c ^*Command Lines\* Every MAKE statement can contain one or more DCL ^*command lines\* which are executed, in the order they are given, when the target of the statement is out of date with respect to any of its prerequisites. Command lines from the makefile are executed in a single sub-process which MAKE creates with the same "context" as the user's process. This means that any symbols or logical names which are defined in the user's process are also defined in the MAKE sub-process, and thus can be used in command lines inside the makefile. When a command line is executed, MAKE checks to see if the command exits with a status value indicating error. If a command signals an error, MAKE aborts, since the likeliest interpretation is that the current target cannot be updated. If the command line is preceded by '-', MAKE will ignore the exit status, and continue executing even if an error occurs. (The MAKE command option /IGNORE causes MAKE to ignore all command execution errors.) The following makefile demonstrates the use of '-'. .bl 1 .nf #----- test.exe: test.obj - del $@;_* link $? #----- .f .bl 1 The '-' is necessary because if the target file TEST.EXE does not exist, the delete command will return an error status. MAKE should ignore this error, if it occurs, because it is unimportant. By default, MAKE prints each command line as it is executed. If the command line is preceded by '@', it will not be printed when executed. The MAKE command option /SILENT suppresses printing of all command lines. The following makefile uses '@' to print a message without displaying the command line responsible. .bl 1 .nf #----- test.exe: test.obj io.obj link $,? @ write sys\$output "TEST.EXE created, deleting objects." - del $,?;_* #----- .f .bl 1 .c ^*Literal Characters\* Notice the use of backslash in the makefile just given. This is the "accept" character for make, as '__' is the accept character for RUNOFF. It tells MAKE that the character which follows it is not to have its usual syntactic meaning, but should instead be treated like any ordinary character. In this case, it tells MAKE that the '$' in 'sys$output' does not indicate use of a macro, but is just another character in the command line. Another use of backslash is suggested by the same command line. The initial '@' tells MAKE not to print the command line when it is executed. This is also the character which tells DCL to run a command file. To get this second meaning, the '@' must be preceded by a backslash, which tells MAKE that the '@' is part of the command line. One further use of backslash is to continue long lines in makefiles. If a backslash is found at the end of a line, MAKE deletes it and interprets the end of line as a blank. This is demonstrated in the following makefile. .bl 1 .nf #----- test.exe: file1.obj file2.obj file3.obj file4.obj file5.obj \ file6.obj file7.obj file8.obj file9.obj file10.obj link /exe=$@ $,? #----- .f