PPMMaakkee ---- AA TTuuttoorriiaall _A_d_a_m _d_e _B_o_o_r Berkeley Softworks 2150 Shattuck Ave, Penthouse Berkeley, CA 94704 adam@bsw.uu.net ...!uunet!bsw!adam 11.. IInnttrroodduuccttiioonn PMake is a program for creating other programs, or anything else you can think of for it to do. The basic idea behind PMake is that, for any given system, be it a program or a document or whatever, there will be some files that depend on the state of other files (on when they were last modi- fied). PMake takes these dependencies, which you must spec- ify, and uses them to build whatever it is you want it to build. PMake is almost fully-compatible with Make, with which you may already be familiar. PMake's most important feature is its ability to run several different jobs at once, making the creation of systems considerably faster. It also has a great deal more functionality than Make. Throughout the text, whenever something is mentioned that is an important difference between PMake and Make (i.e. something that will cause a makefile to fail if you don't do something about it), or is simply important, it will be flagged with a lit- ----tle sign in the left margin, like this: | Th|is tutorial is divided into three main sections corre- | NOTEsp|onding to basic, intermediate and advanced PMake usage. If | yo|u already know Make well, you will only need to skim chap- ----ter 2 (there are some aspects of PMake that I consider basic to its use that didn't exist in Make). Things in chapter 3 make life much easier, while those in chapter 4 are strictly for those who know what they are doing. Chapter 5 has defi- nitions for the jargon I use and chapter 6 contains possible solutions to the problems presented throughout the tutorial. ----------- Permission to use, copy, modify, and distribute this software and its documentation for any pur- pose and without fee is hereby granted, provided that the above copyright notice appears in all copies. The University of California, Berkeley Softworks, and Adam de Boor make no representa- tions about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. PSD:12-2 PMake -- A Tutorial 22.. TThhee BBaassiiccss ooff PPMMaakkee PMake takes as input a file that tells a) which files depend on which other files to be complete and b) what to do about files that are ``out-of-date.'' This file is known as a ``makefile'' and is usually kept in the top-most directory of the system to be built. While you can call the makefile anything you want, PMake will look for Makefile aanndd mmaakkeeffiillee (in that order) in the current directory if you don't tell it otherwise. To specify a different makefile, use the --ff flag (e.g. ``pmake -f program.mk'''')).. A makefile has four different types of lines in it: +o File dependency specifications +o Creation commands +o Variable assignments +o Comments, include statements and conditional direc- tives Any line may be continued over multiple lines by ending it with a backslash. The backslash, following newline and any initial whitespace on the following line are compressed into a single space before the input line is examined by PMake. 22..11.. DDeeppeennddeennccyy LLiinneess As mentioned in the introduction, in any system, there are dependencies between the files that make up the system. For instance, in a program made up of several C source files and one header file, the C files will need to be re-compiled should the header file be changed. For a document of several chapters and one macro file, the chapters will need to be reprocessed if any of the macros changes. These are depen- dencies and are specified by means of dependency lines in the makefile. On a dependency line, there are targets and sources, sepa- rated by a one- or two-character operator. The targets ``depend'' on the sources and are usually created from them. Any number of targets and sources may be specified on a dependency line. All the targets in the line are made to depend on all the sources. Targets and sources need not be actual files, but every source must be either an actual file or another target in the makefile. If you run out of room, use a backslash at the end of the line to continue onto the next one. Any file may be a target and any file may be a source, but the relationship between the two (or however many) is deter- mined by the ``operator'' that separates them. Three types of operators exist: one specifies that the datedness of a target is determined by the state of its sources, while another specifies other files (the sources) that need to be dealt with before the target can be re-created. The third operator is very similar to the first, with the additional condition that the target is out-of-date if it has no sources. These operations are represented by the colon, the exclamation point and the double-colon, respectively, and are mutually exclusive. Their exact semantics are as fol- lows: PMake -- A Tutorial PSD:12-3 : If a colon is used, a target on the line is considered to be ``out-of-date'' (and in need of creation) if +o any of the sources has been modified more recently than the target, or +o the target doesn't exist. Under this operation, steps will be taken to re-create the target only if it is found to be out-of-date by using these two rules. ! If an exclamation point is used, the target will always be re-created, but this will not happen until all of its sources have been examined and re-created, if nec- essary. :: If a double-colon is used, a target is out-of-date if: +o any of the sources has been modified more recently than the target, or +o the target doesn't exist, or +o the target has no sources. If the target is out-of-date according to these rules, it will be re-created. This operator also does some- thing else to the targets, but I'll go into that in the next section (``Shell Commands''). Enough words, now for an example. Take that C program I men- tioned earlier. Say there are three C files (a.c, b.c and c.c) each of which includes the file defs.h. The dependen- cies between the files could then be expressed as follows: program : a.o b.o c.o a.o b.o c.o : defs.h a.o : a.c b.o : b.c c.o : c.c You may be wondering at this point, where a.o, b.o and c.o came in and why _t_h_e_y depend on defs.h _a_n_d _t_h_e _C _f_i_l_e_s _d_o_n_'_t_. _T_h_e _r_e_a_s_o_n _i_s _q_u_i_t_e _s_i_m_p_l_e_: _p_r_o_g_r_a_m cannot be made by link- ing together .c files -- it must be made from .o files. Likewise, if you change defs.h_, _i_t _i_s_n_'_t _t_h_e _._c _f_i_l_e_s _t_h_a_t _n_e_e_d _t_o _b_e _r_e_-_c_r_e_a_t_e_d_, _i_t_'_s _t_h_e _._o _f_i_l_e_s_. _I_f _y_o_u _t_h_i_n_k _o_f _d_e_p_e_n_d_e_n_c_i_e_s _i_n _t_h_e_s_e _t_e_r_m_s _-_- _w_h_i_c_h _f_i_l_e_s _(_t_a_r_g_e_t_s_) _n_e_e_d _t_o _b_e _c_r_e_a_t_e_d _f_r_o_m _w_h_i_c_h _f_i_l_e_s _(_s_o_u_r_c_e_s_) _-_- _y_o_u _s_h_o_u_l_d _h_a_v_e _n_o _p_r_o_b_l_e_m_s_. An important thing to notice about the above example, is that all the .o files appear as targets on more than one line. This is perfectly all right: the target is made to depend on all the sources mentioned on all the dependency ----lines. E.g. a.o _d_e_p_e_n_d_s _o_n _b_o_t_h _d_e_f_s_._h and a.c_. | Th|e order of the dependency lines in the makefile is impor- | NOTEta|nt: the first target on the first dependency line in the | ma|kefile will be the one that gets made if you don't say ----otherwise. That's why program _c_o_m_e_s _f_i_r_s_t _i_n _t_h_e _e_x_a_m_p_l_e _m_a_k_e_f_i_l_e_, _a_b_o_v_e_. Both targets and sources may contain the standard C-Shell wildcard characters ({_, _}, *_, _?, [_, _a_n_d _]), but the non- curly-brace ones may only appear in the final component (the PSD:12-4 PMake -- A Tutorial file portion) of the target or source. The characters mean the following things: {{}} These enclose a comma-separated list of options and cause the pattern to be expanded once for each element of the list. Each expansion contains a different ele- ment. For example, src/{whiffle,beep,fish}.c expands to the three words src/whiffle.c, src/beep.c, and src/fish.c. These braces may be nested and, unlike the other wildcard characters, the resulting words need not be actual files. All other wildcard characters are expanded using the files that exist when PMake is started. ** This matches zero or more characters of any sort. src/*.c will expand to the same three words as above as long as src contains those three files (and no other files that end in .c). ?? Matches any single character. [[]] This is known as a character class and contains either a list of single characters, or a series of character ranges (a-z, for example means all characters between a and z), or both. It matches any single character con- tained in the list. E.g. [A-Za-z] will match all let- ters, while [0123456789] will match all numbers. 22..22.. SShheellll CCoommmmaannddss ``Isn't that nice,'' you say to yourself, ``but how are files actually `re-created,' as he likes to spell it?'' The re-creation is accomplished by commands you place in the makefile. These commands are passed to the Bourne shell (better known as ``/bin/sh'') to be executed and are expected to do what's necessary to update the target file (PMake doesn't actually check to see if the target was cre- ated. It just assumes it's there). Shell commands in a makefile look a lot like shell commands you would type at a terminal, with one important exception: each command in a makefile _m_u_s_t be preceded by at least one tab. Each target has associated with it a shell script made up of one or more of these shell commands. The creation script for a target should immediately follow the dependency line for that target. While any given target may appear on more than one dependency line, only one of these dependency lines may be followed by a creation script, unless the `::' operator ----was used on the dependency line. | If|the double-colon was used, each dependency line for the | NOTEta|rget may be followed by a shell script. That script will | on|ly be executed if the target on the associated dependency ----line is out-of-date with respect to the sources on that line, according to the rules I gave earlier. I'll give you a good example of this later on. To expand on the earlier makefile, you might add commands as follows: PMake -- A Tutorial PSD:12-5 program : a.o b.o c.o cc a.o b.o c.o -o program a.o b.o c.o : defs.h a.o : a.c cc -c a.c b.o : b.c cc -c b.c c.o : c.c cc -c c.c Something you should remember when writing a makefile is, the commands will be executed if the _t_a_r_g_e_t on the depen- dency line is out-of-date, not the sources. In this exam- ple, the command ``cc -c a.c_'_' _w_i_l_l _b_e _e_x_e_c_u_t_e_d _i_f _a_._o is out-of-date. Because of the `:' operator, this means that should a.c _o_r _d_e_f_s_._h _h_a_v_e _b_e_e_n _m_o_d_i_f_i_e_d _m_o_r_e _r_e_c_e_n_t_l_y _t_h_a_n _a_._o_, _t_h_e _c_o_m_m_a_n_d _w_i_l_l _b_e _e_x_e_c_u_t_e_d _(_a_._o _w_i_l_l _b_e _c_o_n_s_i_d_e_r_e_d _o_u_t_-_o_f_-_d_a_t_e_)_. Remember how I said the only difference between a makefile shell command and a regular shell command was the leading tab? I lied. There is another way in which makefile commands differ from regular ones. The first two characters after the initial whitespace are treated specially. If they are any combination of `@' and `-', they cause PMake to do dif- ferent things. In most cases, shell commands are printed before they're actually executed. This is to keep you informed of what's going on. If an `@' appears, however, this echoing is sup- pressed. In the case of an echo command, say ``echo Linking index,'' it would be rather silly to see echo Linking index Linking index so PMake allows you to place an `@' before the command (``@echo Linking index'') to prevent the command from being printed. The other special character is the `-'. In case you didn't know, shell commands finish with a certain ``exit status.'' This status is made available by the operating system to whatever program invoked the command. Normally this status will be 0 if everything went ok and non-zero if something went wrong. For this reason, PMake will consider an error to have occurred if one of the shells it invokes returns a non- zero status. When it detects an error, PMake's usual action is to abort whatever it's doing and exit with a non-zero status itself (any other targets that were being created will continue being made, but nothing new will be started. PMake will exit after the last job finishes). This behavior can be altered, however, by placing a `-' at the front of a command (``-mv index index.old''), certain command-line arguments, or doing other things, to be detailed later. In such a case, the non-zero status is simply ignored and PMake keeps chugging along. PSD:12-6 PMake -- A Tutorial ----Because all the commands are given to a single shell to exe- | cu|te, such things as setting shell variables, changing | NOTEdi|rectories, etc., last beyond the command in which they are | fo|und. This also allows shell compound commands (like for ----loops) to be entered in a natural manner. Since this could cause problems for some makefiles that depend on each com- mand being executed by a single shell, PMake has a --BB flag (it stands for backwards-compatible) that forces each com- mand to be given to a separate shell. It also does several other things, all of which I discourage since they are now ----old-fashioned.... | A |target's shell script is fed to the shell on its (the | NOTEsh|ell's) input stream. This means that any commands, such | as| ci that need to get input from the terminal won't work ----right -- they'll get the shell's input, something they prob- ably won't find to their liking. A simple way around this is to give a command like this: ci $(SRCS) < /dev/tty This would force the program's input to come from the termi- nal. If you can't do this for some reason, your only other alternative is to use PMake in its fullest compatibility mode. See CCoommppaattiibbiilliittyy in chapter 4. 22..33.. VVaarriiaabblleess PMake, like Make before it, has the ability to save text in variables to be recalled later at your convenience. Vari- ables in PMake are used much like variables in the shell and, by tradition, consist of all upper-case letters (you don't _h_a_v_e to use all upper-case letters. In fact there's nothing to stop you from calling a variable @^&$%$_. _J_u_s_t _t_r_a_d_i_t_i_o_n_)_. _V_a_r_i_a_b_l_e_s _a_r_e _a_s_s_i_g_n_e_d_-_t_o _u_s_i_n_g _l_i_n_e_s _o_f _t_h_e _f_o_r_m VARIABLE = value appended-to by VARIABLE += value conditionally assigned-to (if the variable isn't already defined) by VARIABLE ?= value and assigned-to with expansion (i.e. the value is expanded (see below) before being assigned to the variable--useful for placing a value at the beginning of a variable, or other things) by VARIABLE := value PMake -- A Tutorial PSD:12-7 Any whitespace before _v_a_l_u_e is stripped off. When appending, a space is placed between the old value and the stuff being appended. The final way a variable may be assigned to is using VARIABLE != shell-command In this case, _s_h_e_l_l_-_c_o_m_m_a_n_d has all its variables expanded (see below) and is passed off to a shell to execute. The output of the shell is then placed in the variable. Any new- lines (other than the final one) are replaced by spaces before the assignment is made. This is typically used to find the current directory via a line like: CWD != pwd NNoottee:: this is intended to be used to execute commands that produce small amounts of output (e.g. ``pwd''). The imple- mentation is less than intelligent and will likely freeze if you execute something that produces thousands of bytes of output (8 Kb is the limit on many UNIX systems). The value of a variable may be retrieved by enclosing the variable name in parentheses or curly braces and preceeding the whole thing with a dollar sign. For example, to set the variable CFLAGS to the string ``-I/sprite/src/lib/libc -O,'' you would place a line CFLAGS = -I/sprite/src/lib/libc -O in the makefile and use the word $(CFLAGS) wherever you would like the string -I/sprite/src/lib/libc -O to appear. ----This is called variable expansion. | Un|like Make, PMake will not expand a variable unless it | NOTEkn|ows the variable exists. E.g. if you have a ${i} in a | sh|ell command and you have not assigned a value to the vari- ----able i (the empty string is considered a value, by the way), where Make would have substituted the empty string, PMake will leave the ${i} alone. To keep PMake from substituting for a variable it knows, precede the dollar sign with another dollar sign. (e.g. to pass ${HOME} to the shell, use $${HOME}). This causes PMake, in effect, to expand the $ macro, which expands to a single $. For compatibility, Make's style of variable expansion will be used if you invoke PMake with any of the compatibility flags (--VV, --BB or --MM. The --VV flag alters just the variable expansion). There are two different times at which variable expansion occurs: When parsing a dependency line, the expansion occurs immediately upon reading the line. If any variable used on a dependency line is undefined, PMake will print a message and exit. Variables in shell commands are expanded when the command is executed. Variables used inside another variable are expanded whenever the outer variable is expanded (the expansion of an inner variable has no effect on the outer variable. I.e. if the outer variable is used on a dependency PSD:12-8 PMake -- A Tutorial line and in a shell command, and the inner variable changes value between when the dependency line is read and the shell command is executed, two different values will be substi- tuted for the outer variable). Variables come in four flavors, though they are all expanded the same and all look about the same. They are (in order of expanding scope): +o Local variables. +o Command-line variables. +o Global variables. +o Environment variables. The classification of variables doesn't matter much, except that the classes are searched from the top (local) to the bottom (environment) when looking up a variable. The first one found wins. 22..33..11.. LLooccaall VVaarriiaabblleess Each target can have as many as seven local variables. These are variables that are only ``visible'' within that target's shell script and contain such things as the target's name, all of its sources (from all its dependency lines), those sources that were out-of-date, etc. Four local variables are defined for all targets. They are: .TARGET The name of the target. .OODATE The list of the sources for the target that were considered out-of-date. The order in the list is not guaranteed to be the same as the order in which the dependencies were given. .ALLSRC The list of all sources for this target in the order in which they were given. .PREFIX The target without its suffix and without any leading path. E.g. for the target ../../lib/com- pat/fsRead.c, this variable would contain fsRead. Three other local variables are set only for certain targets under special circumstances. These are the ``.IMPSRC,'' ``.ARCHIVE,'' and ``.MEMBER'' variables. When they are set and how they are used is described later. Four of these variables may be used in sources as well as in shell scripts. These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The variables in the sources are expanded once for each target on the dependency line, providing what is known as a ``dynamic source,'' allowing you to specify several dependency lines at once. For exam- ple, $(OBJS) : $(.PREFIX).c will create a dependency between each object file and its corresponding C source file. PMake -- A Tutorial PSD:12-9 22..33..22.. CCoommmmaanndd--lliinnee VVaarriiaabblleess Command-line variables are set when PMake is first invoked by giving a variable assignment as one of the arguments. For example, pmake "CFLAGS = -I/sprite/src/lib/libc -O" would make CFLAGS be a command-line variable with the given value. Any assignments to CFLAGS in the makefile will have no effect, because once it is set, there is (almost) nothing you can do to change a command-line variable (the search order, you see). Command-line variables may be set using any of the four assignment operators, though only = and ?= behave as you would expect them to, mostly because assign- ments to command-line variables are performed before the makefile is read, thus the values set in the makefile are unavailable at the time. += is the same as =, because the old value of the variable is sought only in the scope in which the assignment is taking place (for reasons of effi- ciency that I won't get into here). := and ?= will work if the only variables used are in the environment. != is sort of pointless to use from the command line, since the same effect can no doubt be accomplished using the shell's own command substitution mechanisms (backquotes and all that). 22..33..33.. GGlloobbaall VVaarriiaabblleess Global variables are those set or appended-to in the make- file. There are two classes of global variables: those you set and those PMake sets. As I said before, the ones you set can have any name you want them to have, except they may not contain a colon or an exclamation point. The variables PMake sets (almost) always begin with a period and always contain upper-case letters, only. The variables are as fol- lows: .PMAKE The name by which PMake was invoked is stored in this variable. For compatibility, the name is also stored in the MAKE variable. .MAKEFLAGS All the relevant flags with which PMake was invoked. This does not include such things as --ff or variable assignments. Again for compatibility, this value is stored in the MFLAGS variable as well. Two other variables, ``.INCLUDES'' and ``.LIBS,'' are cov- ered in the section on special targets in chapter 3. Global variables may be deleted using lines of the form: #undef _v_a_r_i_a_b_l_e The `#' must be the first character on the line. Note that this may only be done on global variables. PSD:12-10 PMake -- A Tutorial 22..33..44.. EEnnvviirroonnmmeenntt VVaarriiaabblleess Environment variables are passed by the shell that invoked PMake and are given by PMake to each shell it invokes. They are expanded like any other variable, but they cannot be altered in any way. One special environment variable, PMAKE, is examined by PMake for command-line flags, variable assignments, etc., it should always use. This variable is examined before the actual arguments to PMake are. In addition, all flags given to PMake, either through the PMAKE variable or on the com- mand line, are placed in this environment variable and exported to each shell PMake executes. Thus recursive invo- cations of PMake automatically receive the same flags as the top-most one. Using all these variables, you can compress the sample make- file even more: OBJS = a.o b.o c.o program : $(OBJS) cc $(.ALLSRC) -o $(.TARGET) $(OBJS) : defs.h a.o : a.c cc -c a.c b.o : b.c cc -c b.c c.o : c.c cc -c c.c 22..44.. CCoommmmeennttss Comments in a makefile start with a `#' character and extend to the end of the line. They may appear anywhere you want them, except in a shell command (though the shell will treat it as a comment, too). If, for some reason, you need to use the `#' in a variable or on a dependency line, put a back- slash in front of it. PMake will compress the two into a single `#' (Note: this isn't true if PMake is operating in full-compatibility mode). ----22..55.. PPaarraalllleelliissmm | PM|ake was specifically designed to re-create several targets | NOTEat|once, when possible. You do not have to do anything spe- | ci|al to cause this to happen (unless PMake was configured to ----not act in parallel, in which case you will have to make use of the --LL and --JJ flags (see below)), but you do have to be careful at times. There are several problems you are likely to encounter. One is that some makefiles (and programs) are written in such a way that it is impossible for two targets to be made at once. The program xstr, for example, always modifies the files strings and x.c. There is no way to change it. Thus you cannot run two of them at once without something being trashed. Similarly, if you have commands in the makefile that always send output to the same file, you will not be PMake -- A Tutorial PSD:12-11 able to make more than one target at once unless you change the file you use. You can, for instance, add a $$$$ to the end of the file name to tack on the process ID of the shell executing the command (each $$ expands to a single $, thus giving you the shell variable $$). Since only one shell is used for all the commands, you'll get the same file name for each command in the script. The other problem comes from improperly-specified dependen- cies that worked in Make because of its sequential, depth- first way of examining them. While I don't want to go into depth on how PMake works (look in chapter 4 if you're inter- ested), I will warn you that files in two different ``lev- els'' of the dependency tree may be examined in a different order in PMake than they were in Make. For example, given the makefile a : b c b : d PMake will examine the targets in the order c, d, b, a. If the makefile's author expected PMake to abort before making c if an error occurred while making b, or if b needed to exist before c was made, s/he will be sorely disappointed. The dependencies are incomplete, since in both these cases, c would depend on b. So watch out. Another problem you may face is that, while PMake is set up to handle the output from multiple jobs in a graceful fash- ion, the same is not so for input. It has no way to regu- late input to different jobs, so if you use the redirection from /dev/tty I mentioned earlier, you must be careful not to run two of the jobs at once. 22..66.. WWrriittiinngg aanndd DDeebbuuggggiinngg aa MMaakkeeffiillee Now you know most of what's in a makefile, what do you do next? There are two choices: (1) use one of the uncommonly- available makefile generators or (2) write your own makefile (I leave out the third choice of ignoring PMake and doing everything by hand as being beyond the bounds of common sense). When faced with the writing of a makefile, it is usually best to start from first principles: just what _a_r_e you try- ing to do? What do you want the makefile finally to produce? To begin with a somewhat traditional example, let's say you need to write a makefile to create a program, expr, that takes standard infix expressions and converts them to prefix form (for no readily apparent reason). You've got three source files, in C, that make up the program: main.c, parse.c, and output.c. Harking back to my pithy advice about dependency lines, you write the first line of the file: expr : main.o parse.o output.o because you remember expr is made from .o files, not .c PSD:12-12 PMake -- A Tutorial files. Similarly for the .o files you produce the lines: main.o : main.c parse.o : parse.c output.o : output.c main.o parse.o output.o : defs.h Great. You've now got the dependencies specified. What you need now is commands. These commands, remember, must produce the target on the dependency line, usually by using the sources you've listed. You remember about local variables? Good, so it should come to you as no surprise when you write expr : main.o parse.o output.o cc -o $(.TARGET) $(.ALLSRC) Why use the variables? If your program grows to produce postfix expressions too (which, of course, requires a name change or two), it is one fewer place you have to change the file. You cannot do this for the object files, however, because they depend on their corresponding source files _a_n_d defs.h_, _t_h_u_s _i_f _y_o_u _s_a_i_d cc -c $(.ALLSRC) you'd get (for main.o): cc -c main.c defs.h which is wrong. So you round out the makefile with these lines: main.o : main.c cc -c main.c parse.o : parse.c cc -c parse.c output.o : output.c cc -c output.c The makefile is now complete and will, in fact, create the program you want it to without unnecessary compilations or excessive typing on your part. There are two things wrong with it, however (aside from it being altogether too long, something I'll address in chapter 3): 1) The string ``main.o parse.o output.o'' is repeated twice, necessitating two changes when you add postfix (you were planning on that, weren't you?). This is in direct violation of de Boor's First Rule of writing makefiles: _A_n_y_t_h_i_n_g _t_h_a_t _n_e_e_d_s _t_o _b_e _w_r_i_t_t_e_n _m_o_r_e _t_h_a_n _o_n_c_e _s_h_o_u_l_d _b_e _p_l_a_c_e_d _i_n _a _v_a_r_i_a_b_l_e_. I cannot emphasize this enough as being very important to the maintenance of a makefile and its program. PMake -- A Tutorial PSD:12-13 2) There is no way to alter the way compilations are per- formed short of editing the makefile and making the change in all places. This is evil and violates de Boor's Second Rule, which follows directly from the first: _A_n_y _f_l_a_g_s _o_r _p_r_o_g_r_a_m_s _u_s_e_d _i_n_s_i_d_e _a _m_a_k_e_f_i_l_e _s_h_o_u_l_d _b_e _p_l_a_c_e_d _i_n _a _v_a_r_i_a_b_l_e _s_o _t_h_e_y _m_a_y _b_e _c_h_a_n_g_e_d_, _t_e_m_p_o_r_a_r_i_l_y _o_r _p_e_r_m_a_n_e_n_t_l_y_, _w_i_t_h _t_h_e _g_r_e_a_t_e_s_t _e_a_s_e_. The makefile should more properly read: OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) main.o : main.c $(CC) $(CFLAGS) -c main.c parse.o : parse.c $(CC) $(CFLAGS) -c parse.c output.o : output.c $(CC) $(CFLAGS) -c output.c $(OBJS) : defs.h Alternatively, if you like the idea of dynamic sources men- tioned in section 2.3.1, you could write it like this: OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) $(OBJS) : $(.PREFIX).c defs.h $(CC) $(CFLAGS) -c $(.PREFIX).c These two rules and examples lead to de Boor's First Corol- lary: _V_a_r_i_a_b_l_e_s _a_r_e _y_o_u_r _f_r_i_e_n_d_s_. Once you've written the makefile comes the sometimes-diffi- cult task of making sure the darn thing works. Your most helpful tool to make sure the makefile is at least syntacti- cally correct is the --nn flag, which allows you to see if PMake will choke on the makefile. The second thing the --nn flag lets you do is see what PMake would do without it actu- ally doing it, thus you can make sure the right commands would be executed were you to give PMake its head. When you find your makefile isn't behaving as you hoped, the first question that comes to mind (after ``What time is it, anyway?'') is ``Why not?'' In answering this, two flags will serve you well: ``-d m'' and ``-p 2.'' The first causes PMake to tell you as it examines each target in the makefile and indicate why it is deciding whatever it is deciding. You can then use the information printed for other targets to see where you went wrong. The ``-p 2'' flag makes PMake print out its internal state when it is done, allowing you to see that you forgot to make that one chapter depend on that file of macros you just got a new version of. The out- put from ``-p 2'' is intended to resemble closely a real PSD:12-14 PMake -- A Tutorial makefile, but with additional information provided and with variables expanded in those commands PMake actually printed or executed. Something to be especially careful about is circular depen- dencies. E.g. a : b b : c d d : a In this case, because of how PMake works, c is the only thing PMake will examine, because d and a will effectively fall off the edge of the universe, making it impossible to examine b (or them, for that matter). PMake will tell you (if run in its normal mode) all the targets involved in any cycle it looked at (i.e. if you have two cycles in the graph (naughty, naughty), but only try to make a target in one of them, PMake will only tell you about that one. You'll have to try to make the other to find the second cycle). When run as Make, it will only print the first target in the cycle. 22..77.. IInnvvookkiinngg PPMMaakkee PMake comes with a wide variety of flags to choose from. They may appear in any order, interspersed with command-line variable assignments and targets to create. The flags are as follows: --dd _w_h_a_t This causes PMake to spew out debugging information that may prove useful to you. If you can't figure out why PMake is doing what it's doing, you might try using this flag. The _w_h_a_t parameter is a string of single characters that tell PMake what aspects you are inter- ested in. Most of what I describe will make little sense to you, unless you've dealt with Make before. Just remember where this table is and come back to it as you read on. The characters and the information they produce are as follows: a Archive searching and caching. c Conditional evaluation. d The searching and caching of directories. j Various snippets of information related to the running of the multiple shells. Not particularly interesting. m The making of each target: what target is being examined; when it was last modified; whether it is out-of-date; etc. p Makefile parsing. r Remote execution. s The application of suffix-transformation rules. (See chapter 3) t The maintenance of the list of targets. v Variable assignment. Of these all, the m and s letters will be most useful to you. If the --dd is the final argument or the PMake -- A Tutorial PSD:12-15 argument from which it would get these key letters (see below for a note about which argument would be used) begins with a --, all of these debugging flags will be set, resulting in massive amounts of output. --ff _m_a_k_e_f_i_l_e Specify a makefile to read different from the standard makefiles (Makefile or makefile). If _m_a_k_e_f_i_l_e is ``-'', PMake uses the standard input. This is useful for making quick and dirty makefiles... --hh Prints out a summary of the various flags PMake accepts. It can also be used to find out what level of concurrency was compiled into the version of PMake you are using (look at --JJ and --LL) and various other infor- mation on how PMake was configured. --ii If you give this flag, PMake will ignore non-zero sta- tus returned by any of its shells. It's like placing a `-' before all the commands in the makefile. --kk This is similar to --ii in that it allows PMake to con- tinue when it sees an error, but unlike --ii, where PMake continues blithely as if nothing went wrong, --kk causes it to recognize the error and only continue work on those things that don't depend on the target, either directly or indirectly (through depending on something that depends on it), whose creation returned the error. The `k' is for ``keep going''... --ll PMake has the ability to lock a directory against other people executing it in the same directory (by means of a file called ``LOCK.make'' that it creates and checks for in the directory). This is a Good Thing because two people doing the same thing in the same place can be disastrous for the final product (too many cooks and all that). Whether this locking is the default is up to your system administrator. If locking is on, --ll will turn it off, and vice versa. Note that this locking will not prevent _y_o_u from invoking PMake twice in the same place -- if you own the lock file, PMake will warn you about it but continue to execute. --mm _d_i_r_e_c_t_o_r_y Tells PMake another place to search for included make- files via the <...> style. Several --mm options can be given to form a search path. If this construct is used the default system makefile search path is completely overridden. To be explained in chapter 3, section 3.2. --nn This flag tells PMake not to execute the commands needed to update the out-of-date targets in the make- file. Rather, PMake will simply print the commands it would have executed and exit. This is particularly use- ful for checking the correctness of a makefile. If PMake doesn't do what you expect it to, it's a good chance the makefile is wrong. --pp _n_u_m_b_e_r This causes PMake to print its input in a reasonable form, though not necessarily one that would make imme- diate sense to anyone but me. The _n_u_m_b_e_r is a bitwise- PSD:12-16 PMake -- A Tutorial or of 1 and 2 where 1 means it should print the input before doing any processing and 2 says it should print it after everything has been re-created. Thus -p 3 _w_o_u_l_d _p_r_i_n_t _i_t _t_w_i_c_e_-_-_o_n_c_e _b_e_f_o_r_e _p_r_o_c_e_s_s_i_n_g _a_n_d _o_n_c_e _a_f_t_e_r _(_y_o_u _m_i_g_h_t _f_i_n_d _t_h_e _d_i_f_f_e_r_e_n_c_e _b_e_t_w_e_e_n _t_h_e _t_w_o _i_n_t_e_r_e_s_t_i_n_g_)_. _T_h_i_s _i_s _m_o_s_t_l_y _u_s_e_f_u_l _t_o _m_e_, _b_u_t _y_o_u _m_a_y _f_i_n_d _i_t _i_n_f_o_r_m_a_t_i_v_e _i_n _s_o_m_e _b_i_z_a_r_r_e _c_i_r_c_u_m_s_t_a_n_c_e_s_. --qq If you give PMake this flag, it will not try to re-cre- ate anything. It will just see if anything is out-of- date and exit non-zero if so. --rr When PMake starts up, it reads a default makefile that tells it what sort of system it's on and gives it some idea of what to do if you don't tell it anything. I'll tell you about it in chapter 3. If you give this flag, PMake won't read the default makefile. --ss This causes PMake to not print commands before they're executed. It is the equivalent of putting an `@' before every command in the makefile. --tt Rather than try to re-create a target, PMake will sim- ply ``touch'' it so as to make it appear up-to-date. If the target didn't exist before, it will when PMake fin- ishes, but if the target did exist, it will appear to have been updated. --vv This is a mixed-compatibility flag intended to mimic the System V version of Make. It is the same as giving --BB, and --VV as well as turning off directory locking. Targets can still be created in parallel, however. This is the mode PMake will enter if it is invoked either as ``smake'''' oorr ````vvmmaakkee''. --xx This tells PMake it's ok to export jobs to other machines, if they're available. It is used when running in Make mode, as exporting in this mode tends to make things run slower than if the commands were just exe- cuted locally. --BB Forces PMake to be as backwards-compatible with Make as possible while still being itself. This includes: +o Executing one shell per shell command +o Expanding anything that looks even vaguely like a variable, with the empty string replacing any vari- able PMake doesn't know. +o Refusing to allow you to escape a `#' with a back- slash. +o Permitting undefined variables on dependency lines and conditionals (see below). Normally this causes PMake to abort. --CC This nullifies any and all compatibility mode flags you may have given or implied up to the time the --CC is encountered. It is useful mostly in a makefile that you wrote for PMake to avoid bad things happening when someone runs PMake as ``make'''' oorr hhaass tthhiinnggss sseett iinn tthhee eennvviirroonnmmeenntt tthhaatt tteellll iitt ttoo bbee ccoommppaattiibbllee.. --CC iiss _n_o_t ppllaacceedd iinn tthhee PPMMAAKKEE _e_n_v_i_r_o_n_m_e_n_t _v_a_r_i_a_b_l_e _o_r _t_h_e _._M_A_K_E_- _F_L_A_G_S oorr MMFFLLAAGGSS _g_l_o_b_a_l _v_a_r_i_a_b_l_e_s_. PMake -- A Tutorial PSD:12-17 --DD _v_a_r_i_a_b_l_e Allows you to define a variable to have ``1_'_' _a_s _i_t_s _v_a_l_u_e_. _T_h_e _v_a_r_i_a_b_l_e _i_s _a _g_l_o_b_a_l _v_a_r_i_a_b_l_e_, _n_o_t _a _c_o_m_- _m_a_n_d_-_l_i_n_e _v_a_r_i_a_b_l_e_. _T_h_i_s _i_s _u_s_e_f_u_l _m_o_s_t_l_y _f_o_r _p_e_o_p_l_e _w_h_o _a_r_e _u_s_e_d _t_o _t_h_e _C _c_o_m_p_i_l_e_r _a_r_g_u_m_e_n_t_s _a_n_d _t_h_o_s_e _u_s_i_n_g _c_o_n_d_i_t_i_o_n_a_l_s_, _w_h_i_c_h _I_'_l_l _g_e_t _i_n_t_o _i_n _s_e_c_t_i_o_n _4_._3 --II _d_i_r_e_c_t_o_r_y Tells PMake another place to search for included make- files. Yet another thing to be explained in chapter 3 (section 3.2, to be precise). --JJ _n_u_m_b_e_r Gives the absolute maximum number of targets to create at once on both local and remote machines. --LL _n_u_m_b_e_r This specifies the maximum number of targets to create on the local machine at once. This may be 0, though you should be wary of doing this, as PMake may hang until a remote machine becomes available, if one is not avail- able when it is started. --MM This is the flag that provides absolute, complete, full compatibility with Make. It still allows you to use all but a few of the features of PMake, but it is non-par- allel. This is the mode PMake enters if you call it ``make.'' --PP When creating targets in parallel, several shells are executing at once, each wanting to write its own two cent's-worth to the screen. This output must be cap- tured by PMake in some way in order to prevent the screen from being filled with garbage even more indeci- pherable than you usually see. PMake has two ways of doing this, one of which provides for much cleaner out- put and a clear separation between the output of dif- ferent jobs, the other of which provides a more immedi- ate response so one can tell what is really happpening. The former is done by notifying you when the creation of a target starts, capturing the output and transfer- ring it to the screen all at once when the job fin- ishes. The latter is done by catching the output of the shell (and its children) and buffering it until an entire line is received, then printing that line pre- ceded by an indication of which job produced the out- put. Since I prefer this second method, it is the one used by default. The first method will be used if you give the --PP flag to PMake. --VV As mentioned before, the --VV flag tells PMake to use Make's style of expanding variables, substituting the empty string for any variable it doesn't know. --WW There are several times when PMake will print a message at you that is only a warning, i.e. it can continue to work in spite of your having done something silly (such as forgotten a leading tab for a shell command). Some- times you are well aware of silly things you have done and would like PMake to stop bothering you. This flag tells it to shut up about anything non-fatal. PSD:12-18 PMake -- A Tutorial --XX This flag causes PMake to not attempt to export any jobs to another machine. Several flags may follow a single `-'. Those flags that require arguments take them from successive parameters. E.g. pmake -fDnI server.mk DEBUG /chip2/X/server/include will cause PMake to read server.mk as the input makefile, define the variable DEBUG as a global variable and look for included makefiles in the directory /chip2/X/server/include. 22..88.. SSuummmmaarryy A makefile is made of four types of lines: +o Dependency lines +o Creation commands +o Variable assignments +o Comments, include statements and conditional direc- tives A dependency line is a list of one or more targets, an oper- ator (`:', `::', or `!'), and a list of zero or more sources. Sources may contain wildcards and certain local variables. A creation command is a regular shell command preceded by a tab. In addition, if the first two characters after the tab (and other whitespace) are a combination of `@' or `-', PMake will cause the command to not be printed (if the char- acter is `@') or errors from it to be ignored (if `-'). A blank line, dependency line or variable assignment termi- nates a creation script. There may be only one creation script for each target with a `:' or `!' operator. Variables are places to store text. They may be uncondition- ally assigned-to using the `=' operator, appended-to using the `+=' operator, conditionally (if the variable is unde- fined) assigned-to with the `?=' operator, and assigned-to with variable expansion with the `:=' operator. The output of a shell command may be assigned to a variable using the `!=' operator. Variables may be expanded (their value inserted) by enclosing their name in parentheses or curly braces, prceeded by a dollar sign. A dollar sign may be escaped with another dollar sign. Variables are not expanded if PMake doesn't know about them. There are seven local variables: .TARGET, .ALLSRC, .OODATE, .PREFIX, .IMPSRC, .ARCHIVE, and .MEMBER. Four of them (.TARGET, .PREFIX, .ARCHIVE, and .MEMBER) may be used to specify ``dynamic sources.'' Variables are good. Know them. Love them. Live them. Debugging of makefiles is best accomplished using the --nn, --dd mm, and --pp 22 flags. 22..99.. EExxeerrcciisseess TTBBAA PMake -- A Tutorial PSD:12-19 33.. SShhoorrtt--ccuuttss aanndd OOtthheerr NNiiccee TThhiinnggss Based on what I've told you so far, you may have gotten the impression that PMake is just a way of storing away commands and making sure you don't forget to compile something. Good. That's just what it is. However, the ways I've described have been inelegant, at best, and painful, at worst. This chapter contains things that make the writing of makefiles easier and the makefiles themselves shorter and easier to modify (and, occasionally, simpler). In this chapter, I assume you are somewhat more familiar with Sprite (or UNIX, if that's what you're using) than I did in chapter 2, just so you're on your toes. So without further ado... 33..11.. TTrraannssffoorrmmaattiioonn RRuulleess As you know, a file's name consists of two parts: a base name, which gives some hint as to the contents of the file, and a suffix, which usually indicates the format of the file. Over the years, as UNIX(R) has developed, naming con- ventions, with regard to suffixes, have also developed that have become almost as incontrovertible as Law. E.g. a file ending in .c iiss aassssuummeedd ttoo ccoonnttaaiinn CC ssoouurrccee ccooddee;; oonnee wwiitthh aa ..oo suffix is assumed to be a compiled, relocatable object file that may be linked into any program; a file with a .ms ssuuffffiixx iiss uussuuaallllyy aa tteexxtt ffiillee ttoo bbee pprroocceesssseedd bbyy TTrrooffff wwiitthh tthhee --mmss mmaaccrroo ppaacckkaaggee,, aanndd ssoo oonn.. OOnnee ooff tthhee bbeesstt aassppeeccttss ooff bbootthh MMaakkee aanndd PPMMaakkee ccoommeess ffrroomm tthheeiirr uunnddeerrssttaannddiinngg ooff hhooww tthhee ssuuffffiixx ooff aa ffiillee ppeerrttaaiinnss ttoo iittss ccoonntteennttss aanndd tthheeiirr aabbiilliittyy ttoo ddoo tthhiinnggss wwiitthh aa ffiillee bbaasseedd ssoolleeyy oonn iittss ssuuffffiixx.. TThhiiss aabbiilliittyy ccoommeess ffrroomm ssoommeetthhiinngg kknnoowwnn aass aa ttrraannssffoorrmmaattiioonn rruullee.. AA ttrraannssffoorrmmaattiioonn rruullee ssppeecciiffiieess hhooww ttoo cchhaannggee aa ffiillee wwiitthh oonnee ssuuffffiixx iinnttoo aa ffiillee wwiitthh aannootthheerr ssuuffffiixx.. A transformation rule looks much like a dependency line, except the target is made of two known suffixes stuck together. Suffixes are made known to PMake by placing them as sources on a dependency line whose target is the special target .SUFFIXES.. EE..gg.. .SUFFIXES : .o .c .c.o : $(CC) $(CFLAGS) -c $(.IMPSRC) The creation script attached to the target is used to trans- form a file with the first suffix (in this case, .c) into a file with the second suffix (here, .o). In addition, the target inherits whatever attributes have been applied to the transformation rule. The simple rule given above says that to transform a C source file into an object file, you com- pile it using cc with the -c flag. This rule is taken straight from the system makefile. Many transformation rules (and suffixes) are defined there, and I refer you to it for more examples (type ``pmake -h'' to find out where it is). There are several things to note about the transformation rule given above: PSD:12-20 PMake -- A Tutorial 1) The .IMPSRC variable. This variable is set to the ``implied source'' (the file from which the target is being created; the one with the first suffix), which, in this case, is the .c file. 2) The CFLAGS variable. Almost all of the transforma- tion rules in the system makefile are set up using variables that you can alter in your makefile to tailor the rule to your needs. In this case, if you want all your C files to be compiled with the --gg flag, to provide information for dbx,, yyoouu wwoouulldd sseett tthhee CCFFLLAAGGSS variable to contain -g ((````CCFFLLAAGGSS == --gg'') and PMake would take care of the rest. To give you a quick example, the makefile in 2.3.4 could be changed to this: OBJS = a.o b.o c.o program : $(OBJS) $(CC) -o $(.TARGET) $(.ALLSRC) $(OBJS) : defs.h The transformation rule I gave above takes the place of the 6 lines1 a.o : a.c cc -c a.c b.o : b.c cc -c b.c c.o : c.c cc -c c.c Now you may be wondering about the dependency between the .o and .c files -- it's not mentioned anywhere in the new make- file. This is because it isn't needed: one of the effects of applying a transformation rule is the target comes to depend on the implied source. That's why it's called the implied _s_o_u_r_c_e. For a more detailed example. Say you have a makefile like this: a.out : a.o b.o $(CC) $(.ALLSRC) and a directory set up like this: total 4 -rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile -rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c -rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o -rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c While just typing ``pmake'' will do the right thing, it's ----------- 1 This is also somewhat cleaner, I think, than the dynamic source solution presented in 2.6 PMake -- A Tutorial PSD:12-21 much more informative to type ``pmake -d s''. This will show you what PMake is up to as it processes the files. In this case, PMake prints the following: Suff_FindDeps (a.out) using existing source a.o applying .o -> .out to "a.o" Suff_FindDeps (a.o) trying a.c...got it applying .c -> .o to "a.c" Suff_FindDeps (b.o) trying b.c...got it applying .c -> .o to "b.c" Suff_FindDeps (a.c) trying a.y...not there trying a.l...not there trying a.c,v...not there trying a.y,v...not there trying a.l,v...not there Suff_FindDeps (b.c) trying b.y...not there trying b.l...not there trying b.c,v...not there trying b.y,v...not there trying b.l,v...not there --- a.o --- cc -c a.c --- b.o --- cc -c b.c --- a.out --- cc a.o b.o Suff_FindDeps is the name of a function in PMake that is called to check for implied sources for a target using transformation rules. The transformations it tries are, naturally enough, limited to the ones that have been defined (a transformation may be defined multiple times, by the way, but only the most recent one will be used). You will notice, however, that there is a definite order to the suffixes that are tried. This order is set by the relative positions of the suffixes on the .SUFFIXES line -- the earlier a suffix appears, the earlier it is checked as the source of a trans- formation. Once a suffix has been defined, the only way to change its position in the pecking order is to remove all the suffixes (by having a .SUFFIXES dependency line with no sources) and redefine them in the order you want. (Previ- ously-defined transformation rules will be automatically redefined as the suffixes they involve are re-entered.) Another way to affect the search order is to make the depen- dency explicit. In the above example, a.out depends on a.o and b.o. Since a transformation exists from .o to .out, PMake uses that, as indicated by the ``using existing source a.o'' message. PSD:12-22 PMake -- A Tutorial The search for a transformation starts from the suffix of the target and continues through all the defined transforma- tions, in the order dictated by the suffix ranking, until an existing file with the same base (the target name minus the suffix and any leading directories) is found. At that point, one or more transformation rules will have been found to change the one existing file into the target. For example, ignoring what's in the system makefile for now, say you have a makefile like this: .SUFFIXES : .out .o .c .y .l .l.c : lex $(.IMPSRC) mv lex.yy.c $(.TARGET) .y.c : yacc $(.IMPSRC) mv y.tab.c $(.TARGET) .c.o : cc -c $(.IMPSRC) .o.out : cc -o $(.TARGET) $(.IMPSRC) and the single file jive.l. If you were to type ``pmake -rd ms jive.out,'' you would get the following output for jive.out: Suff_FindDeps (jive.out) trying jive.o...not there trying jive.c...not there trying jive.y...not there trying jive.l...got it applying .l -> .c to "jive.l" applying .c -> .o to "jive.c" applying .o -> .out to "jive.o" and this is why: PMake starts with the target jive.out, fig- ures out its suffix (.out) and looks for things it can transform to a .out file. In this case, it only finds .o, so it looks for the file jive.o. It fails to find it, so it looks for transformations into a .o file. Again it has only one choice: .c. So it looks for jive.c and, as you know, fails to find it. At this point it has two choices: it can create the .c file from either a .y file or a .l file. Since .y came first on the .SUFFIXES line, it checks for jive.y first, but can't find it, so it looks for jive.l and, lo and behold, there it is. At this point, it has defined a trans- formation path as follows: .l -> .c -> .o -> .out and applies the transformation rules accordingly. For complete- ness, and to give you a better idea of what PMake actually did with this three-step transformation, this is what PMake printed for the rest of the process: PMake -- A Tutorial PSD:12-23 Suff_FindDeps (jive.o) using existing source jive.c applying .c -> .o to "jive.c" Suff_FindDeps (jive.c) using existing source jive.l applying .l -> .c to "jive.l" Suff_FindDeps (jive.l) Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date Examining jive.c...non-existent...out-of-date --- jive.c --- lex jive.l ... meaningless lex output deleted ... mv lex.yy.c jive.c Examining jive.o...non-existent...out-of-date --- jive.o --- cc -c jive.c Examining jive.out...non-existent...out-of-date --- jive.out --- cc -o jive.out jive.o One final question remains: what does PMake do with targets that have no known suffix? PMake simply pretends it actually has a known suffix and searches for transformations accord- ingly. The suffix it chooses is the source for the .NULL target mentioned later. In the system makefile, .out is cho- sen as the ``null suffix'' because most people use PMake to create programs. You are, however, free and welcome to change it to a suffix of your own choosing. The null suffix is ignored, however, when PMake is in compatibility mode (see chapter 4). 33..22.. IInncclluuddiinngg OOtthheerr MMaakkeeffiilleess Just as for programs, it is often useful to extract certain parts of a makefile into another file and just include it in other makefiles somehow. Many compilers allow you say some- thing like #include "defs.h" to include the contents of defs.h in the source file. PMake allows you to do the same thing for makefiles, with the added ability to use variables in the filenames. An include directive in a makefile looks either like this: #include or this #include "file" The difference between the two is where PMake searches for the file: the first way, PMake will look for the file only in the system makefile directory (or directories) (to find out what that directory is, give PMake the --hh flag). The PSD:12-24 PMake -- A Tutorial system makefile directory search path can be overridden via the --mm option. For files in double-quotes, the search is more complex: 1) The directory of the makefile that's including the file. 2) The current directory (the one in which you invoked PMake). 3) The directories given by you using --II flags, in the order in which you gave them. 4) Directories given by .PATH dependency lines (see chapter 4). 5) The system makefile directory. in that order. You are free to use PMake variables in the filename--PMake will expand them before searching for the file. You must specify the searching method with either angle brackets or double-quotes _o_u_t_s_i_d_e of a variable expansion. I.e. the fol- lowing SYSTEM = #include $(SYSTEM) won't work. 33..33.. SSaavviinngg CCoommmmaannddss There may come a time when you will want to save certain commands to be executed when everything else is done. For instance: you're making several different libraries at one time and you want to create the members in parallel. Problem is, ranlib iiss aannootthheerr oonnee ooff tthhoossee pprrooggrraammss tthhaatt ccaann''tt bbee rruunn mmoorree tthhaann oonnccee iinn tthhee ssaammee ddiirreeccttoorryy aatt tthhee ssaammee ttiimmee ((eeaacchh oonnee ccrreeaatteess aa ffiillee ccaalllleedd ____..SSYYMMDDEEFF into which it stuffs information for the linker to use. Two of them run- ning at once will overwrite each other's file and the result will be garbage for both parties). You might want a way to save the ranlib commands til the end so they can be run one after the other, thus keeping them from trashing each other's file. PMake allows you to do this by inserting an ellipsis (``...'') as a command between commands to be run at once and those to be run later. So for the ranlib case above, you might do this: PMake -- A Tutorial PSD:12-25 lib1.a : $(LIB1OBJS) rm -f $(.TARGET) ar cr $(.TARGET) $(.ALLSRC) ... ranlib $(.TARGET) lib2.a : $(LIB2OBJS) rm -f $(.TARGET) ar cr $(.TARGET) $(.ALLSRC) ... ranlib $(.TARGET) This would save both ranlib $(.TARGET) commands until the end, when they would run one after the other (using the correct value for the .TARGET variable, of course). Commands saved in this manner are only executed if PMake manages to re-create everything without an error. 33..44.. TTaarrggeett AAttttrriibbuutteess PMake allows you to give attributes to targets by means of special sources. Like everything else PMake uses, these sources begin with a period and are made up of all upper- case letters. There are various reasons for using them, and I will try to give examples for most of them. Others you'll have to find uses for yourself. Think of it as ``an exercise for the reader.'' By placing one (or more) of these as a source on a dependency line, you are ``marking the target(s) with that attribute.'' That's just the way I phrase it, so you know. Any attributes given as sources for a transformation rule are applied to the target of the transformation rule when the rule is applied. .DONTCARE If a target is marked with this attribute and PMake can't figure out how to create it, it will ignore this fact and assume the file isn't really needed or actually exists and PMake just can't find it. This may prove wrong, but the error will be noted later on, not when PMake tries to create the target so marked. This attribute also prevents PMake from attempting to touch the target if it is given the --tt flag. .EXEC This attribute causes its shell script to be executed while having no effect on targets that depend on it. This makes the target into a sort of subroutine. An example. Say you have some LISP files that need to be compiled and loaded into a LISP process. To do this, you echo LISP commands into a file and execute a LISP with this file as its input when everything's done. Say also that you have to load other files from PSD:12-26 PMake -- A Tutorial another system before you can compile your files and further, that you don't want to go through the loading and dumping unless one of _y_o_u_r files has changed. Your makefile might look a little bit like this (remember, this is an educational example, and don't worry about the COMPILE _r_u_l_e_, _a_l_l _w_i_l_l _s_o_o_n _b_e_c_o_m_e _c_l_e_a_r_, _g_r_a_s_s_h_o_p_p_e_r_)_: system : init a.fasl b.fasl c.fasl for i in $(.ALLSRC); do echo -n '(load "' >> input echo -n ${i} >> input echo '")' >> input done echo '(dump "$(.TARGET)")' >> input lisp < input a.fasl : a.l init COMPILE b.fasl : b.l init COMPILE c.fasl : c.l init COMPILE COMPILE : .USE echo '(compile "$(.ALLSRC)")' >> input init : .EXEC echo '(load-system)' > input .EXEC sources, don't appear in the local vari- ables of targets that depend on them (nor are they touched if PMake is given the --tt flag). Note that all the rules, not just that for sys- tem,, iinncclluuddee iinniitt as a source. This is because none of the other targets can be made until init hhaass bbeeeenn mmaaddee,, tthhuuss tthheeyy ddeeppeenndd oonn iitt.. .EXPORT This is used to mark those targets whose cre- ation should be sent to another machine if at all possible. This may be used by some exporta- tion schemes if the exportation is expensive. You should ask your system administrator if it is necessary. .EXPORTSAME Tells the export system that the job should be exported to a machine of the same architecture as the current one. Certain operations (e.g. running text through nroff) can be performed the same on any architecture (CPU and operating sys- tem type), while others (e.g. compiling a pro- gram with cc) must be performed on a machine with the same architecture. Not all export sys- tems will support this attribute. .IGNORE Giving a target the .IGNORE attribute causes PMake to ignore errors from any of the target's commands, as if they all had `-' before them. .INVISIBLE This allows you to specify one target as a source for another without the one affecting the other's local variables. Useful if, say, you PMake -- A Tutorial PSD:12-27 have a makefile that creates two programs, one of which is used to create the other, so it must exist before the other is created. You could say prog1 : $(PROG1OBJS) prog2 MAKEINSTALL prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL where MAKEINSTALL is some complex .USE rule (see below) that depends on the .ALLSRC variable con- taining the right things. Without the .INVISIBLE attribute for prog2, the MAKEINSTALL rule couldn't be applied. This is not as useful as it should be, and the semantics may change (or the whole thing go away) in the not-too-distant future. .JOIN This is another way to avoid performing some operations in parallel while permitting every- thing else to be done so. Specifically it forces the target's shell script to be executed only if one or more of the sources was out-of-date. In addition, the target's name, in both its .TARGET variable and all the local variables of any tar- get that depends on it, is replaced by the value of its .ALLSRC variable. As an example, suppose you have a program that has four libraries that compile in the same directory along with, and at the same time as, the program. You again have the problem with ranlib that I mentioned ear- lier, only this time it's more severe: you can't just put the ranlib off to the end since the program will need those libraries before it can be re-created. You can do something like this: program : $(OBJS) libraries cc -o $(.TARGET) $(.ALLSRC) libraries : lib1.a lib2.a lib3.a lib4.a .JOIN ranlib $(.OODATE) In this case, PMake will re-create the $(OBJS) as necessary, along with lib1.a, lib2.a, lib3.a and lib4.a. It will then execute ranlib on any library that was changed and set program's .ALL- SRC variable to contain what's in $(OBJS) fol- lowed by ``lib1.a lib2.a lib3.a lib4.a.'' In case you're wondering, it's called .JOIN because it joins together different threads of the ``input graph'' at the target marked with the attribute. Another aspect of the .JOIN attribute is it keeps the target from being cre- ated if the --tt flag was given. .MAKE The .MAKE attribute marks its target as being a recursive invocation of PMake. This forces PMake to execute the script associated with the PSD:12-28 PMake -- A Tutorial target (if it's out-of-date) even if you gave the --nn or --tt flag. By doing this, you can start at the top of a system and type pmake -n and have it descend the directory tree (if your makefiles are set up correctly), printing what it would have executed if you hadn't included the --nn flag. .NOEXPORT If possible, PMake will attempt to export the creation of all targets to another machine (this depends on how PMake was configured). Sometimes, the creation is so simple, it is pointless to send it to another machine. If you give the tar- get the .NOEXPORT attribute, it will be run locally, even if you've given PMake the --LL 00 flag. .NOTMAIN Normally, if you do not specify a target to make in any other way, PMake will take the first tar- get on the first dependency line of a makefile as the target to create. That target is known as the ``Main Target'' and is labeled as such if you print the dependencies out using the --pp flag. Giving a target this attribute tells PMake that the target is definitely _n_o_t the Main Target. This allows you to place targets in an included makefile and have PMake create some- thing else by default. .PRECIOUS When PMake is interrupted (you type control-C at the keyboard), it will attempt to clean up after itself by removing any half-made targets. If a target has the .PRECIOUS attribute, however, PMake will leave it alone. An additional side effect of the `::' operator is to mark the tar- gets as .PRECIOUS. .SILENT Marking a target with this attribute keeps its commands from being printed when they're exe- cuted, just as if they had an `@' in front of them. .USE By giving a target this attribute, you turn it into PMake's equivalent of a macro. When the target is used as a source for another target, the other target acquires the commands, sources and attributes (except .USE) of the source. If the target already has commands, the .USE tar- get's commands are added to the end. If more than one .USE-marked source is given to a tar- get, the rules are applied sequentially. The typical .USE rule (as I call them) will use the sources of the target to which it is applied (as stored in the .ALLSRC variable for the tar- get) as its ``arguments,'' if you will. For example, you probably noticed that the commands PMake -- A Tutorial PSD:12-29 for creating lib1.a and lib2.a in the example in section 3.3 were exactly the same. You can use the .USE attribute to eliminate the repetition, like so: lib1.a : $(LIB1OBJS) MAKELIB lib2.a : $(LIB2OBJS) MAKELIB MAKELIB : .USE rm -f $(.TARGET) ar cr $(.TARGET) $(.ALLSRC) ... ranlib $(.TARGET) Several system makefiles (not to be confused with The System Makefile) make use of these .USE rules to make your life easier (they're in the default, system makefile directory...take a look). Note that the .USE rule source itself (MAKELIB) does not appear in any of the tar- gets's local variables. There is no limit to the number of times I could use the MAKELIB rule. If there were more libraries, I could con- tinue with ``lib3.a : $(LIB3OBJS) MAKELIB'' and so on and so forth. 33..55.. SSppeecciiaall TTaarrggeettss As there were in Make, so there are certain targets that have special meaning to PMake. When you use one on a depen- dency line, it is the only target that may appear on the left-hand-side of the operator. As for the attributes and variables, all the special targets begin with a period and consist of upper-case letters only. I won't describe them all in detail because some of them are rather complex and I'll describe them in more detail than you'll want in chap- ter 4. The targets are as follows: .BEGIN Any commands attached to this target are executed before anything else is done. You can use it for any initialization that needs doing. .DEFAULT This is sort of a .USE rule for any target (that was used only as a source) that PMake can't figure out any other way to create. It's only ``sort of'' a .USE rule because only the shell script attached to the .DEFAULT target is used. The .IMPSRC vari- able of a target that inherits .DEFAULT's commands is set to the target's own name. .END This serves a function similar to .BEGIN, in that commands attached to it are executed once every- thing has been re-created (so long as no errors occurred). It also serves the extra function of being a place on which PMake can hang commands you put off to the end. Thus the script for this tar- get will be executed before any of the commands you save with the ``...''. PSD:12-30 PMake -- A Tutorial .EXPORT The sources for this target are passed to the exportation system compiled into PMake. Some sys- tems will use these sources to configure them- selves. You should ask your system administrator about this. .IGNORE This target marks each of its sources with the .IGNORE attribute. If you don't give it any sources, then it is like giving the --ii flag when you invoke PMake -- errors are ignored for all commands. .INCLUDES The sources for this target are taken to be suf- fixes that indicate a file that can be included in a program source file. The suffix must have already been declared with .SUFFIXES (see below). Any suffix so marked will have the directories on its search path (see .PATH, below) placed in the .INCLUDES variable, each preceded by a --II flag. This variable can then be used as an argument for the compiler in the normal fashion. The .h ssuuffffiixx iiss aallrreeaaddyy mmaarrkkeedd iinn tthhiiss wwaayy iinn tthhee ssyysstteemm mmaakkee-- ffiillee.. EE..gg.. iiff yyoouu hhaavvee .SUFFIXES : .bitmap .PATH.bitmap : /usr/local/X/lib/bitmaps .INCLUDES : .bitmap PMake will place ``-I/usr/local/X/lib/bitmaps'' in the .INCLUDES variable and you can then say cc $(.INCLUDES) -c xprogram.c (Note: the .INCLUDES variable is not actually filled in until the entire makefile has been read.) .INTERRUPT When PMake is interrupted, it will execute the commands in the script for this target, if it exists. .LIBS This does for libraries what .INCLUDES does for include files, except the flag used is --LL, as required by those linkers that allow you to tell them where to find libraries. The variable used is .LIBS.. BBee ffoorreewwaarrnneedd tthhaatt PPMMaakkee mmaayy nnoott hhaavvee bbeeeenn ccoommppiilleedd ttoo ddoo tthhiiss iiff tthhee lliinnkkeerr oonn yyoouurr ssyysstteemm ddooeessnn''tt aacccceepptt tthhee --LL ffllaagg,, tthhoouugghh tthhee ..LLIIBBSS vvaarrii-- aabbllee wwiillll aallwwaayyss bbee ddeeffiinneedd oonnccee tthhee mmaakkeeffiillee hhaass bbeeeenn rreeaadd.. .MAIN If you didn't give a target (or targets) to create when you invoked PMake, it will take the sources of this target as the targets to create. .MAKEFLAGS This target provides a way for you to always spec- ify flags for PMake when the makefile is used. The flags are just as they would be typed to the shell PMake -- A Tutorial PSD:12-31 (except you can't use shell variables unless they're in the environment), though the --ff and --rr flags have no effect. .NULL This allows you to specify what suffix PMake should pretend a file has if, in fact, it has no known suffix. Only one suffix may be so desig- nated. The last source on the dependency line is the suffix that is used (you should, however, only give one suffix...). .PATH If you give sources for this target, PMake will take them as directories in which to search for files it cannot find in the current directory. If you give no sources, it will clear out any direc- tories added to the search path before. Since the effects of this all get very complex, I'll leave it til chapter four to give you a complete expla- nation. .PATH_s_u_f_f_i_x This does a similar thing to .PATH, but it does it only for files with the given suffix. The suffix must have been defined already. Look at SSeeaarrcchh PPaatthhss (section 4.1) for more information. .PRECIOUS Similar to .IGNORE, this gives the .PRECIOUS attribute to each source on the dependency line, unless there are no sources, in which case the .PRECIOUS attribute is given to every target in the file. .RECURSIVE This target applies the .MAKE attribute to all its sources. It does nothing if you don't give it any sources. .SHELL PMake is not constrained to only using the Bourne shell to execute the commands you put in the make- file. You can tell it some other shell to use with this target. Check out AA SShheellll iiss aa SShheellll iiss aa SShheellll (section 4.4) for more information. .SILENT When you use .SILENT as a target, it applies the .SILENT attribute to each of its sources. If there are no sources on the dependency line, then it is as if you gave PMake the --ss flag and no commands will be echoed. .SUFFIXES This is used to give new file suffixes for PMake to handle. Each source is a suffix PMake should recognize. If you give a .SUFFIXES dependency line with no sources, PMake will forget about all the suffixes it knew (this also nukes the null suf- fix). For those targets that need to have suf- fixes defined, this is how you do it. In addition to these targets, a line of the form _a_t_t_r_i_b_u_t_e : _s_o_u_r_c_e_s applies the _a_t_t_r_i_b_u_t_e to all the targets listed as _s_o_u_r_c_e_s. PSD:12-32 PMake -- A Tutorial 33..66.. MMooddiiffyyiinngg VVaarriiaabbllee EExxppaannssiioonn Variables need not always be expanded verbatim. PMake defines several modifiers that may be applied to a vari- able's value before it is expanded. You apply a modifier by placing it after the variable name with a colon between the two, like so: ${_V_A_R_I_A_B_L_E:_m_o_d_i_f_i_e_r} Each modifier is a single character followed by something specific to the modifier itself. You may apply as many mod- ifiers as you want -- each one is applied to the result of the previous and is separated from the previous by another colon. There are seven ways to modify a variable's expansion, most of which come from the C shell variable modification charac- ters: M_p_a_t_t_e_r_n This is used to select only those words (a word is a series of characters that are neither spaces nor tabs) that match the given _p_a_t_t_e_r_n. The pattern is a wildcard pattern like that used by the shell, where * _m_e_a_n_s _0 _o_r _m_o_r_e _c_h_a_r_a_c_t_e_r_s _o_f _a_n_y _s_o_r_t_; _? is any single character; [abcd] _m_a_t_c_h_e_s _a_n_y _s_i_n_g_l_e _c_h_a_r_a_c_t_e_r _t_h_a_t _i_s _e_i_t_h_e_r _`_a_'_, _`_b_'_, _`_c_' _o_r _`_d_' _(_t_h_e_r_e _m_a_y _b_e _a_n_y _n_u_m_b_e_r _o_f _c_h_a_r_a_c_t_e_r_s _b_e_t_w_e_e_n _t_h_e _b_r_a_c_k_e_t_s_)_; _[_0_-_9_] matches any single character that is between `0' and `9' (i.e. any digit. This form may be freely mixed with the other bracket form), and `\' is used to escape any of the characters `*', `?', `[' or `:', leaving them as regular characters to match themselves in a word. For example, the system makefile _u_s_e_s _`_`_$_(_C_F_L_A_G_S_:_M_-_[_I_D_]_*_)'' to extract all the -I _a_n_d _-_D flags that would be passed to the C compiler. This allows it to properly locate include files and generate the correct dependencies. N_p_a_t_t_e_r_n This is identical to :M except it substitutes all words that don't match the given pattern. S/_s_e_a_r_c_h_-_s_t_r_i_n_g/_r_e_p_l_a_c_e_m_e_n_t_-_s_t_r_i_n_g/[g] Causes the first occurrence of _s_e_a_r_c_h_-_s_t_r_i_n_g in the variable to be replaced by _r_e_p_l_a_c_e_m_e_n_t_-_s_t_r_i_n_g, unless the g _f_l_a_g _i_s _g_i_v_e_n _a_t _t_h_e _e_n_d_, _i_n _w_h_i_c_h _c_a_s_e _a_l_l _o_c_c_u_r_e_n_c_e_s _o_f _t_h_e _s_t_r_i_n_g _a_r_e _r_e_p_l_a_c_e_d_. _T_h_e _s_u_b_s_t_i_t_u_t_i_o_n _i_s _p_e_r_f_o_r_m_e_d _o_n _e_a_c_h _w_o_r_d _i_n _t_h_e _v_a_r_i_a_b_l_e _i_n _t_u_r_n_. _I_f _s_e_a_r_c_h_-_s_t_r_i_n_g _b_e_g_i_n_s _w_i_t_h _a _^_, _t_h_e _s_t_r_i_n_g _m_u_s_t _m_a_t_c_h _s_t_a_r_t_i_n_g _a_t _t_h_e _b_e_g_i_n_n_i_n_g _o_f _t_h_e _w_o_r_d_. _I_f _s_e_a_r_c_h_-_s_t_r_i_n_g _e_n_d_s _w_i_t_h _a _$_, _t_h_e _s_t_r_i_n_g _m_u_s_t _m_a_t_c_h _t_o _t_h_e _e_n_d _o_f _t_h_e _w_o_r_d _(_t_h_e_s_e _t_w_o _m_a_y _b_e _c_o_m_b_i_n_e_d _t_o _f_o_r_c_e _a_n _e_x_a_c_t _m_a_t_c_h_)_. _I_f _a _b_a_c_k_s_l_a_s_h _p_r_e_c_e_e_d_s _t_h_e_s_e _t_w_o _c_h_a_r_a_c_t_e_r_s_, _h_o_w_e_v_e_r_, _t_h_e_y _l_o_s_e _t_h_e_i_r _s_p_e_c_i_a_l _m_e_a_n_i_n_g_. _V_a_r_i_a_b_l_e _e_x_p_a_n_- _s_i_o_n _a_l_s_o _o_c_c_u_r_s _i_n _t_h_e _n_o_r_m_a_l _f_a_s_h_i_o_n _i_n_s_i_d_e _b_o_t_h PMake -- A Tutorial PSD:12-33 _t_h_e _s_e_a_r_c_h_-_s_t_r_i_n_g _a_n_d _t_h_e _r_e_p_l_a_c_e_m_e_n_t_-_s_t_r_i_n_g_, eexxcceepptt _t_h_a_t _a _b_a_c_k_s_l_a_s_h _i_s _u_s_e_d _t_o _p_r_e_v_e_n_t _t_h_e _e_x_p_a_n_s_i_o_n _o_f _a _$,, nnoott aannootthheerr ddoollllaarr ssiiggnn,, aass iiss uussuuaall.. NNoottee tthhaatt _s_e_a_r_c_h_-_s_t_r_i_n_g iiss jjuusstt aa ssttrriinngg,, nnoott aa ppaatttteerrnn,, ssoo nnoonnee ooff tthhee uussuuaall rreegguullaarr-- eexxpprreessssiioonn//wwiillddccaarrdd cchhaarraacctteerrss hhaavvee aannyy ssppeecciiaall mmeeaanniinngg ssaavvee ^^ _a_n_d _$.. IInn tthhee rreeppllaacceemmeenntt ssttrriinngg,, tthhee && _c_h_a_r_a_c_t_e_r _i_s _r_e_p_l_a_c_e_d _b_y _t_h_e _s_e_a_r_c_h_-_s_t_r_i_n_g _u_n_l_e_s_s _i_t _i_s _p_r_e_c_e_d_e_d _b_y _a _b_a_c_k_s_l_a_s_h_. _Y_o_u _a_r_e _a_l_l_o_w_e_d _t_o _u_s_e _a_n_y _c_h_a_r_a_c_t_e_r _e_x_c_e_p_t _c_o_l_o_n _o_r _e_x_c_l_a_m_a_t_i_o_n _p_o_i_n_t _t_o _s_e_p_a_r_a_t_e _t_h_e _t_w_o _s_t_r_i_n_g_s_. _T_h_i_s _s_o_-_c_a_l_l_e_d _d_e_l_i_m_i_t_e_r _c_h_a_r_a_c_t_e_r _m_a_y _b_e _p_l_a_c_e_d _i_n _e_i_t_h_e_r _s_t_r_i_n_g _b_y _p_r_e_c_e_e_d_i_n_g _i_t _w_i_t_h _a _b_a_c_k_- _s_l_a_s_h_. T Replaces each word in the variable expansion by its last component (its ``tail''). For example, given OBJS = ../lib/a.o b /usr/lib/libm.a TAILS = $(OBJS:T) the variable TAILS would expand to ``a.o b libm.a.'' H This is similar to :T, except that every word is replaced by everything but the tail (the ``head''). Using the same definition of OBJS, the string ``$(OBJS:H)'' would expand to ``../lib /usr/lib.'' Note that the final slash on the heads is removed and anything without a head is replaced by the empty string. E :E replaces each word by its suffix (``exten- sion''). So ``$(OBJS:E)'' would give you ``.o .a.'' R This replaces each word by everything but the suf- fix (the ``root'' of the word). ``$(OBJS:R)'' expands to `` ../lib/a b /usr/lib/libm.'' In addition, the System V style of substitution is also sup- ported. This looks like: $(_V_A_R_I_A_B_L_E:_s_e_a_r_c_h_-_s_t_r_i_n_g=_r_e_p_l_a_c_e_m_e_n_t) It must be the last modifier in the chain. The search is anchored at the end of each word, so only suffixes or whole words may be replaced. 33..77.. MMoorree oonn DDeebbuuggggiinngg 33..88.. MMoorree EExxeerrcciisseess (3.1) You've got a set programs, each of which is created from its own assembly-language source file (suffix .asm)).. EEaacchh pprrooggrraamm ccaann bbee aasssseemmbblleedd iinnttoo ttwwoo vveerr-- ssiioonnss,, oonnee wwiitthh eerrrroorr--cchheecckkiinngg ccooddee aasssseemmbblleedd iinn aanndd oonnee wwiitthhoouutt.. YYoouu ccoouulldd aasssseemmbbllee tthheemm iinnttoo ffiilleess wwiitthh PSD:12-34 PMake -- A Tutorial ddiiffffeerreenntt ssuuffffiixxeess ((..eeoobbjj and .obj,, ffoorr iinnssttaannccee)),, bbuutt yyoouurr lliinnkkeerr oonnllyy uunnddeerrssttaannddss ffiilleess tthhaatt eenndd iinn ..oobbjj. To top it all off, the final executables _m_u_s_t have the suffix .exe_. _H_o_w _c_a_n _y_o_u _s_t_i_l_l _u_s_e _t_r_a_n_s_f_o_r_m_a_t_i_o_n _r_u_l_e_s _t_o _m_a_k_e _y_o_u_r _l_i_f_e _e_a_s_i_e_r _(_H_i_n_t_: _a_s_s_u_m_e _t_h_e _e_r_r_o_r_- _c_h_e_c_k_i_n_g _v_e_r_s_i_o_n_s _h_a_v_e _e_c tacked onto their prefix)? (3.2) Assume, for a moment or two, you want to perform a sort of ``indirection'' by placing the name of a variable into another one, then you want to get the value of the first by expanding the second somehow. Unfortunately, PMake doesn't allow constructs like $($(FOO)) What do you do? Hint: no further variable expansion is performed after modifiers are applied, thus if you cause a $ to occur in the expansion, that's what will be in the result. 44.. PPMMaakkee ffoorr GGooddss This chapter is devoted to those facilities in PMake that allow you to do a great deal in a makefile with very little work, as well as do some things you couldn't do in Make without a great deal of work (and perhaps the use of other programs). The problem with these features, is they must be handled with care, or you will end up with a mess. Once more, I assume a greater familiarity with UNIX or Sprite than I did in the previous two chapters. 44..11.. SSeeaarrcchh PPaatthhss PMake supports the dispersal of files into multiple directo- ries by allowing you to specify places to look for sources with .PATH ttaarrggeettss iinn tthhee mmaakkeeffiillee.. TThhee ddiirreeccttoorriieess yyoouu ggiivvee aass ssoouurrcceess ffoorr tthheessee ttaarrggeettss mmaakkee uupp aa ````sseeaarrcchh ppaatthh..'''' OOnnllyy tthhoossee ffiilleess uusseedd eexxcclluussiivveellyy aass ssoouurrcceess aarree aaccttuuaallllyy ssoouugghhtt oonn aa sseeaarrcchh ppaatthh,, tthhee aassssuummppttiioonn bbeeiinngg tthhaatt aannyytthhiinngg lliisstteedd aass aa ttaarrggeett iinn tthhee mmaakkeeffiillee ccaann bbee ccrreeaatteedd bbyy tthhee mmaakkeeffiillee aanndd tthhuuss sshhoouulldd bbee iinn tthhee ccuurrrreenntt ddiirreeccttoorryy.. There are two types of search paths in PMake: one is used for all types of files (including included makefiles) and is specified with a plain .PATH ttaarrggeett ((ee..gg.. ````..PPAATTHH :: RRCCSS''), while the other is specific to a certain type of file, as indicated by the file's suffix. A specific search path is indicated by immediately following the .PATH wwiitthh tthhee ssuuffffiixx ooff tthhee ffiillee.. FFoorr iinnssttaannccee .PATH.h : /sprite/lib/include /sprite/att/lib/include would tell PMake to look in the directories /sprite/lib/include and /sprite/att/lib/include for any files whose suffix is .h. The current directory is always consulted first to see if a file exists. Only if it cannot be found there are the PMake -- A Tutorial PSD:12-35 directories in the specific search path, followed by those in the general search path, consulted. A search path is also used when expanding wildcard charac- ters. If the pattern has a recognizable suffix on it, the path for that suffix will be used for the expansion. Other- wise the default search path is employed. When a file is found in some directory other than the cur- rent one, all local variables that would have contained the target's name (.ALLSRC, and .IMPSRC) will instead contain the path to the file, as found by PMake. Thus if you have a file ../lib/mumble.c and a makefile .PATH.c : ../lib mumble : mumble.c $(CC) -o $(.TARGET) $(.ALLSRC) the command executed to create mumble would be ``cc -o mum- ble ../lib/mumble.c.'' (As an aside, the command in this case isn't strictly necessary, since it will be found using transformation rules if it isn't given. This is because .out is the null suffix by default and a transformation exists from .c to .out. Just thought I'd throw that in.) If a file exists in two directories on the same search path, the file in the first directory on the path will be the one PMake uses. So if you have a large system spread over many directories, it would behoove you to follow a naming conven- tion that avoids such conflicts. Something you should know about the way search paths are implemented is that each directory is read, and its contents cached, exactly once -- when it is first encountered -- so any changes to the directories while PMake is running will not be noted when searching for implicit sources, nor will they be found when PMake attempts to discover when the file was last modified, unless the file was created in the cur- rent directory. While people have suggested that PMake should read the directories each time, my experience sug- gests that the caching seldom causes problems. In addition, not caching the directories slows things down enormously because of PMake's attempts to apply transformation rules through non-existent files -- the number of extra file-sys- tem searches is truly staggering, especially if many files without suffixes are used and the null suffix isn't changed from .out. 44..22.. AArrcchhiivveess aanndd LLiibbrraarriieess UNIX and Sprite allow you to merge files into an archive using the ar ccoommmmaanndd.. FFuurrtthheerr,, iiff tthhee ffiilleess aarree rreellooccaattaabbllee oobbjjeecctt ffiilleess,, yyoouu ccaann rruunn rraannlliibb on the archive and get yourself a library that you can link into any program you want. The main problem with archives is they double the space you need to store the archived files, since there's one copy in the archive and one copy out by itself. The problem with libraries is you usually think of them as -lm rraatthheerr tthhaann //uussrr//lliibb//lliibbmm..aa and the linker thinks they're PSD:12-36 PMake -- A Tutorial out-of-date if you so much as look at them. PMake solves the problem with archives by allowing you to tell it to examine the files in the archives (so you can remove the individual files without having to regenerate them later). To handle the problem with libraries, PMake adds an additional way of deciding if a library is out-of- date: +o If the table of contents is older than the library, or is missing, the library is out-of-date. A library is any target that looks like ``-lname'' or that ends in a suffix that was marked as a library using the .LIBS target. .a is so marked in the system makefile. Members of an archive are specified as ``_a_r_c_h_i_v_e(_m_e_m_b_e_r[ _m_e_m_b_e_r...])''. Thus ``'libdix.a(window.o)_'_' _s_p_e_c_i_f_i_e_s _t_h_e _f_i_l_e _w_i_n_d_o_w_._o in the archive libdix.a_. _Y_o_u _m_a_y _a_l_s_o _u_s_e _w_i_l_d_c_a_r_d_s _t_o _s_p_e_c_i_f_y _t_h_e _m_e_m_b_e_r_s _o_f _t_h_e _a_r_c_h_i_v_e_. _J_u_s_t _r_e_m_e_m_- _b_e_r _t_h_a_t _m_o_s_t _t_h_e _w_i_l_d_c_a_r_d _c_h_a_r_a_c_t_e_r_s _w_i_l_l _o_n_l_y _f_i_n_d _e_x_i_s_t_- _i_n_g _f_i_l_e_s_. A file that is a member of an archive is treated specially. If the file doesn't exist, but it is in the archive, the modification time recorded in the archive is used for the file when determining if the file is out-of-date. When fig- uring out how to make an archived member target (not the file itself, but the file in the archive -- the _a_r_c_h_i_v_e(_m_e_m_- _b_e_r) target), special care is taken with the transformation rules, as follows: +o _a_r_c_h_i_v_e(_m_e_m_b_e_r) is made to depend on _m_e_m_b_e_r. +o The transformation from the _m_e_m_b_e_r's suffix to the _a_r_c_h_i_v_e's suffix is applied to the _a_r_c_h_i_v_e(_m_e_m_b_e_r) target. +o The _a_r_c_h_i_v_e(_m_e_m_b_e_r)'s .TARGET _v_a_r_i_a_b_l_e _i_s _s_e_t _t_o _t_h_e _n_a_m_e _o_f _t_h_e _m_e_m_b_e_r _i_f _m_e_m_b_e_r _i_s _a_c_t_u_a_l_l_y _a _t_a_r_g_e_t_, _o_r _t_h_e _p_a_t_h _t_o _t_h_e _m_e_m_b_e_r _f_i_l_e _i_f _m_e_m_b_e_r _i_s _o_n_l_y _a _s_o_u_r_c_e_. +o The .ARCHIVE _v_a_r_i_a_b_l_e _f_o_r _t_h_e _a_r_c_h_i_v_e_(_m_e_m_b_e_r_) _t_a_r_g_e_t _i_s _s_e_t _t_o _t_h_e _n_a_m_e _o_f _t_h_e _a_r_c_h_i_v_e_. +o The .MEMBER _v_a_r_i_a_b_l_e _i_s _s_e_t _t_o _t_h_e _a_c_t_u_a_l _s_t_r_i_n_g _i_n_s_i_d_e _t_h_e _p_a_r_e_n_t_h_e_s_e_s_. _I_n _m_o_s_t _c_a_s_e_s_, _t_h_i_s _w_i_l_l _b_e _t_h_e _s_a_m_e _a_s _t_h_e _._T_A_R_G_E_T variable. +o The _a_r_c_h_i_v_e(_m_e_m_b_e_r)'s place in the local variables of the targets that depend on it is taken by the value of its .TARGET _v_a_r_i_a_b_l_e_. Thus, a program library could be created with the following makefile: .o.a : ... rm -f $(.TARGET:T) OBJS = obj1.o obj2.o obj3.o libprog.a : libprog.a($(OBJS)) ar cru $(.TARGET) $(.OODATE) ranlib $(.TARGET) This will cause the three object files to be compiled (if the corresponding source files were modified after the object file or, if that doesn't exist, the archived object PMake -- A Tutorial PSD:12-37 file), the out-of-date ones archived in libprog.a, a table of contents placed in the archive and the newly-archived object files to be removed. All this is used in the makelib.mk system makefile to create a single library with ease. This makefile looks like this: # # Rules for making libraries. The object files that make up the library are # removed once they are archived. # # To make several libararies in parallel, you should define the variable # "many_libraries". This will serialize the invocations of ranlib. # # To use, do something like this: # # OBJECTS = # # fish.a: fish.a($(OBJECTS)) MAKELIB # # #ifndef _MAKELIB_MK _MAKELIB_MK = #include .po.a .o.a : ... rm -f $(.MEMBER) ARFLAGS ?= crl # # Re-archive the out-of-date members and recreate the library's table of # contents using ranlib. If many_libraries is defined, put the ranlib off # til the end so many libraries can be made at once. # MAKELIB : .USE .PRECIOUS ar $(ARFLAGS) $(.TARGET) $(.OODATE) #ifndef no_ranlib # ifdef many_libraries ... # endif many_libraries ranlib $(.TARGET) #endif no_ranlib #endif _MAKELIB_MK 44..33.. OOnn tthhee CCoonnddiittiioonn...... Like the C compiler before it, PMake allows you to configure the makefile, based on the current environment, using condi- tional statements. A conditional looks like this: PSD:12-38 PMake -- A Tutorial #if _b_o_o_l_e_a_n _e_x_p_r_e_s_s_i_o_n _l_i_n_e_s #elif _a_n_o_t_h_e_r _b_o_o_l_e_a_n _e_x_p_r_e_s_s_i_o_n _m_o_r_e _l_i_n_e_s #else _s_t_i_l_l _m_o_r_e _l_i_n_e_s #endif They may be nested to a maximum depth of 30 and may occur anywhere (except in a comment, of course). The ``#'' must the very first character on the line. Each _b_o_o_l_e_a_n _e_x_p_r_e_s_s_i_o_n is made up of terms that look like function calls, the standard C boolean operators &&_, _|_|, and !_, _a_n_d _t_h_e _s_t_a_n_d_a_r_d _r_e_l_a_t_i_o_n_a_l _o_p_e_r_a_t_o_r_s _=_=, !=_, _>, >=_, _<, and <=_, _w_i_t_h _=_= and != _b_e_i_n_g _o_v_e_r_l_o_a_d_e_d _t_o _a_l_l_o_w _s_t_r_i_n_g _c_o_m_- _p_a_r_i_s_o_n_s _a_s _w_e_l_l_. _&_& represents logical AND; || _i_s _l_o_g_i_c_a_l _O_R _a_n_d _! is logical NOT. The arithmetic and string opera- tors take precedence over all three of these operators, while NOT takes precedence over AND, which takes precedence over OR. This precedence may be overridden with parenthe- ses, and an expression may be parenthesized to your heart's content. Each term looks like a call on one of four func- tions: make The syntax is make(_t_a_r_g_e_t) _w_h_e_r_e _t_a_r_g_e_t _i_s _a _t_a_r_g_e_t _i_n _t_h_e _m_a_k_e_f_i_l_e_. _T_h_i_s _i_s _t_r_u_e _i_f _t_h_e _g_i_v_e_n _t_a_r_g_e_t _w_a_s _s_p_e_c_i_f_i_e_d _o_n _t_h_e _c_o_m_m_a_n_d _l_i_n_e_, _o_r _a_s _t_h_e _s_o_u_r_c_e _f_o_r _a _._M_A_I_N _t_a_r_g_e_t _(_n_o_t_e _t_h_a_t _t_h_e _s_o_u_r_c_e_s _f_o_r _._M_A_I_N _a_r_e _o_n_l_y _u_s_e_d _i_f _n_o _t_a_r_g_e_t_s _w_e_r_e _g_i_v_e_n _o_n _t_h_e _c_o_m_- _m_a_n_d _l_i_n_e_)_. defined The syntax is defined(_v_a_r_i_a_b_l_e_) _a_n_d _i_s _t_r_u_e _i_f _v_a_r_i_a_b_l_e _i_s _d_e_f_i_n_e_d_. _C_e_r_t_a_i_n _v_a_r_i_a_b_l_e_s _a_r_e _d_e_f_i_n_e_d _i_n _t_h_e _s_y_s_t_e_m _m_a_k_e_f_i_l_e _t_h_a_t _i_d_e_n_t_i_f_y _t_h_e _s_y_s_t_e_m _o_n _w_h_i_c_h _P_M_a_k_e _i_s _b_e_i_n_g _r_u_n_. exists The syntax is exists(_f_i_l_e_) _a_n_d _i_s _t_r_u_e _i_f _t_h_e _f_i_l_e _c_a_n _b_e _f_o_u_n_d _o_n _t_h_e _g_l_o_b_a_l _s_e_a_r_c_h _p_a_t_h _(_i_._e_. _t_h_a_t _d_e_f_i_n_e_d _b_y _._P_A_T_H _t_a_r_g_e_t_s_, _n_o_t _b_y _._P_A_T_H_s_u_f_f_i_x _t_a_r_- _g_e_t_s_)_. empty This syntax is much like the others, except the string inside the parentheses is of the same form as you would put between parentheses when expanding a variable, complete with modifiers and everything. The function returns true if the resulting string is empty (NOTE: an undefined variable in this con- text will cause at the very least a warning message about a malformed conditional, and at the worst will cause the process to stop once it has read the makefile. If you want to check for a variable being defined or empty, use the expression ``!defined(_v_a_r_) _|_| _e_m_p_t_y_(_v_a_r_)_'_' _a_s _t_h_e _d_e_f_i_n_i_t_i_o_n _o_f _|_| _w_i_l_l _p_r_e_v_e_n_t _t_h_e _e_m_p_t_y_(_) _f_r_o_m _b_e_i_n_g _e_v_a_l_u_a_t_e_d _a_n_d _c_a_u_s_i_n_g _a_n _e_r_r_o_r_, _i_f _t_h_e _v_a_r_i_a_b_l_e _i_s _u_n_d_e_- _f_i_n_e_d_)_. _T_h_i_s _c_a_n _b_e _u_s_e_d _t_o _s_e_e _i_f _a _v_a_r_i_a_b_l_e _c_o_n_- _t_a_i_n_s _a _g_i_v_e_n _w_o_r_d_, _f_o_r _e_x_a_m_p_l_e_: PMake -- A Tutorial PSD:12-39 #if !empty(_v_a_r:M_w_o_r_d) The arithmetic and string operators may only be used to test the value of a variable. The lefthand side must contain the variable expansion, while the righthand side contains either a string, enclosed in double-quotes, or a number. The stan- dard C numeric conventions (except for specifying an octal number) apply to both sides. E.g. #if $(OS) == 4.3 #if $(MACHINE) == "sun3" #if $(LOAD_ADDR) < 0xc000 are all valid conditionals. In addition, the numeric value of a variable can be tested as a boolean as follows: #if $(LOAD) would see if LOAD contains a non-zero value and #if !$(LOAD) would test if LOAD contains a zero value. In addition to the bare ``#if,'' there are other forms that apply one of the first two functions to each term. They are as follows: ifdef defined ifndef !defined ifmake make ifnmake !make There are also the ``else if'' forms: elif, elifdef, elifn- def, elifmake, and elifnmake. For instance, if you wish to create two versions of a pro- gram, one of which is optimized (the production version) and the other of which is for debugging (has symbols for dbx), you have two choices: you can create two makefiles, one of which uses the -g flag for the compilation, while the other uses the -O flag, or you can use another target (call it debug) to create the debug version. The construct below will take care of this for you. I have also made it so defining the variable DEBUG (say with pmake -D DEBUG) will also cause the debug version to be made. #if defined(DEBUG) || make(debug) CFLAGS += -g #else CFLAGS += -O #endif There are, of course, problems with this approach. The most PSD:12-40 PMake -- A Tutorial glaring annoyance is that if you want to go from making a debug version to making a production version, you have to remove all the object files, or you will get some optimized and some debug versions in the same program. Another annoy- ance is you have to be careful not to make two targets that ``conflict'' because of some conditionals in the makefile. For instance #if make(print) FORMATTER = ditroff -Plaser_printer #endif #if make(draft) FORMATTER = nroff -Pdot_matrix_printer #endif would wreak havok if you tried ``pmake draft print'' since you would use the same formatter for each target. As I said, this all gets somewhat complicated. 44..44.. AA SShheellll iiss aa SShheellll iiss aa SShheellll In normal operation, the Bourne Shell (better known as ``sh'''')) iiss uusseedd ttoo eexxeeccuuttee tthhee ccoommmmaannddss ttoo rree--ccrreeaattee ttaarr-- ggeettss.. PPMMaakkee aallssoo aalllloowwss yyoouu ttoo ssppeecciiffyy aa ddiiffffeerreenntt sshheellll ffoorr iitt ttoo uussee wwhheenn eexxeeccuuttiinngg tthheessee ccoommmmaannddss.. TThheerree aarree sseevveerraall tthhiinnggss PPMMaakkee mmuusstt kknnooww aabboouutt tthhee sshheellll yyoouu wwiisshh ttoo uussee.. TThheessee tthhiinnggss aarree ssppeecciiffiieedd aass tthhee ssoouurrcceess ffoorr tthhee ..SSHHEELLLL target by keyword, as follows: ppaatthh==_p_a_t_h PMake needs to know where the shell actually resides, so it can execute it. If you specify this and nothing else, PMake will use the last component of the path and look in its table of the shells it knows and use the specification it finds, if any. Use this if you just want to use a different version of the Bourne or C Shell (yes, PMake knows how to use the C Shell too). nnaammee==_n_a_m_e This is the name by which the shell is to be known. It is a single word and, if no other keywords are speci- fied (other than ppaatthh), it is the name by which PMake attempts to find a specification for it (as mentioned above). You can use this if you would just rather use the C Shell than the Bourne Shell (``.SHELL: name=csh'''' wwiillll ddoo iitt)).. qquuiieett==_e_c_h_o_-_o_f_f _c_o_m_m_a_n_d As mentioned before, PMake actually controls whether commands are printed by introducing commands into the shell's input stream. This keyword, and the next two, control what those commands are. The qquuiieett keyword is the command used to turn echoing off. Once it is turned off, echoing is expected to remain off until the echo- on command is given. eecchhoo==_e_c_h_o_-_o_n _c_o_m_m_a_n_d The command PMake should give to turn echoing back on again. PMake -- A Tutorial PSD:12-41 ffiilltteerr==_p_r_i_n_t_e_d _e_c_h_o_-_o_f_f _c_o_m_m_a_n_d Many shells will echo the echo-off command when it is given. This keyword tells PMake in what format the shell actually prints the echo-off command. Wherever PMake sees this string in the shell's output, it will delete it and any following whitespace, up to and including the next newline. See the example at the end of this section for more details. eecchhooFFllaagg==_f_l_a_g _t_o _t_u_r_n _e_c_h_o_i_n_g _o_n Unless a target has been marked .SILENT, PMake wants to start the shell running with echoing on. To do this, it passes this flag to the shell as one of its arguments. If either this or the next flag begins with a `-', the flags will be passed to the shell as separate argu- ments. Otherwise, the two will be concatenated (if they are used at the same time, of course). eerrrrFFllaagg==_f_l_a_g _t_o _t_u_r_n _e_r_r_o_r _c_h_e_c_k_i_n_g _o_n Likewise, unless a target is marked .IGNORE, PMake wishes error-checking to be on from the very start. To this end, it will pass this flag to the shell as an argument. The same rules for an initial `-' apply as for the eecchhooFFllaagg. cchheecckk==_c_o_m_m_a_n_d _t_o _t_u_r_n _e_r_r_o_r _c_h_e_c_k_i_n_g _o_n Just as for echo-control, error-control is achieved by inserting commands into the shell's input stream. This is the command to make the shell check for errors. It also serves another purpose if the shell doesn't have error-control as commands, but I'll get into that in a minute. Again, once error checking has been turned on, it is expected to remain on until it is turned off again. iiggnnoorree==_c_o_m_m_a_n_d _t_o _t_u_r_n _e_r_r_o_r _c_h_e_c_k_i_n_g _o_f_f This is the command PMake uses to turn error checking off. It has another use if the shell doesn't do error- control, but I'll tell you about that...now. hhaassEErrrrCCttll==_y_e_s _o_r _n_o This takes a value that is either yyeess or nnoo. Now you might think that the existence of the cchheecckk and iiggnnoorree keywords would be enough to tell PMake if the shell can do error-control, but you'd be wrong. If hhaassEErrrrCCttll is yyeess, PMake uses the check and ignore commands in a straight-forward manner. If this is nnoo, however, their use is rather different. In this case, the check com- mand is used as a template, in which the string %%ss is replaced by the command that's about to be executed, to produce a command for the shell that will echo the com- mand to be executed. The ignore command is also used as a template, again with %%ss replaced by the command to be executed, to produce a command that will execute the command to be executed and ignore any error it returns. When these strings are used as templates, you must pro- vide newline(s) (``\n'''')) iinn tthhee aapppprroopprriiaattee ppllaaccee((ss)).. The strings that follow these keywords may be enclosed in single or double quotes (the quotes will be stripped off) PSD:12-42 PMake -- A Tutorial and may contain the usual C backslash-characters (\n is new- line, \r is return, \b is backspace, \' escapes a single- quote inside single-quotes, \" escapes a double-quote inside double-quotes). Now for an example. This is actually the contents of the system make- file, and causes PMake to use the Bourne Shell in such a way that each command is printed as it is executed. That is, if more than one command is given on a line, each will be printed separately. Similarly, each time the body of a loop is executed, the commands within that loop will be printed, etc. The specification runs like this: # # This is a shell specification to have the bourne shell echo # the commands just before executing them, rather than when it reads # them. Useful if you want to see how variables are being expanded, etc. # .SHELL : path=/bin/sh \ quiet="set -" \ echo="set -x" \ filter="+ set - " \ echoFlag=x \ errFlag=e \ hasErrCtl=yes \ check="set -e" \ ignore="set +e" It tells PMake the following: +o The shell is located in the file /bin/sh. It need not tell PMake that the name of the shell is sh as PMake can figure that out for itself (it's the last component of the path). +o The command to stop echoing is set -. +o The command to start echoing is set -x. +o When the echo off command is executed, the shell will print + set - (The `+' comes from using the -x flag (rather than the -v flag PMake usually uses)). PMake will remove all occurences of this string from the output, so you don't notice extra commands you didn't put there. +o The flag the Bourne Shell will take to start echoing in this way is the -x flag. The Bourne Shell will only take its flag arguments concatenated as its first argument, so neither this nor the eerrrrFFllaagg specification begins with a -. +o The flag to use to turn error-checking on from the start is -e. +o The shell can turn error-checking on and off, and the com- mands to do so are set +e and set -e, respectively. I should note that this specification is for Bourne Shells that are not part of Berkeley UNIX, as shells from Berkeley don't do error control. You can get a similar effect, how- ever, by changing the last three lines to be: PMake -- A Tutorial PSD:12-43 hasErrCtl=no \ check="echo \"+ %s\"\n" \ ignore="sh -c '%s || exit 0\n" This will cause PMake to execute the two commands echo "+ _c_m_d" sh -c '_c_m_d || true' for each command for which errors are to be ignored. (In case you are wondering, the thing for ignore tells the shell to execute another shell without error checking on and always exit 0, since the |||| causes the exit 0 ttoo bbee eexxeeccuutteedd oonnllyy iiff tthhee ffiirrsstt ccoommmmaanndd eexxiitteedd nnoonn--zzeerroo,, aanndd iiff tthhee ffiirrsstt ccoommmmaanndd eexxiitteedd zzeerroo,, tthhee sshheellll wwiillll aallssoo eexxiitt zzeerroo,, ssiinnccee tthhaatt''ss tthhee llaasstt ccoommmmaanndd iitt eexxeeccuutteedd)).. 44..55.. CCoommppaattiibbiilliittyy There are three (well, 3 1/2) levels of backwards-compati- bility built into PMake. Most makefiles will need none at all. Some may need a little bit of work to operate correctly when run in parallel. Each level encompasses the previous levels (e.g. --BB (one shell per command) implies --VV) The three levels are described in the following three sections. 44..55..11.. DDEEFFCCOONN 33 ---- VVaarriiaabbllee EExxppaannssiioonn As noted before, PMake will not expand a variable unless it knows of a value for it. This can cause problems for make- files that expect to leave variables undefined except in special circumstances (e.g. if more flags need to be passed to the C compiler or the output from a text processor should be sent to a different printer). If the variables are enclosed in curly braces (``${PRINTER}'''')),, tthhee sshheellll wwiillll lleett tthheemm ppaassss.. IIff tthheeyy aarree eenncclloosseedd iinn ppaarreenntthheesseess,, hhoowweevveerr,, tthhee sshheellll wwiillll ddeeccllaarree aa ssyynnttaaxx eerrrroorr aanndd tthhee mmaakkee wwiillll ccoommee ttoo aa ggrriinnddiinngg hhaalltt.. You have two choices: change the makefile to define the variables (their values can be overridden on the command line, since that's where they would have been set if you used Make, anyway) or always give the --VV flag (this can be done with the .MAKEFLAGS ttaarrggeett,, iiff yyoouu wwaanntt)).. 44..55..22.. DDEEFFCCOONN 22 ---- TThhee NNuummbbeerr ooff tthhee BBeeaasstt Then there are the makefiles that expect certain commands, such as changing to a different directory, to not affect other commands in a target's creation script. You can solve this is either by going back to executing one shell per com- mand (which is what the --BB flag forces PMake to do), which slows the process down a good bit and requires you to use semicolons and escaped newlines for shell constructs, or by changing the makefile to execute the offending command(s) in a subshell (by placing the line inside parentheses), like so: PSD:12-44 PMake -- A Tutorial install :: .MAKE (cd src; $(.PMAKE) install) (cd lib; $(.PMAKE) install) (cd man; $(.PMAKE) install) This will always execute the three makes (even if the --nn flag was given) because of the combination of the ``::'' operator and the .MAKE aattttrriibbuuttee.. EEaacchh ccoommmmaanndd wwiillll cchhaannggee ttoo tthhee pprrooppeerr ddiirreeccttoorryy ttoo ppeerrffoorrmm tthhee iinnssttaallll,, lleeaavviinngg tthhee mmaaiinn sshheellll iinn tthhee ddiirreeccttoorryy iinn wwhhiicchh iitt ssttaarrtteedd.. 44..55..33.. DDEEFFCCOONN 11 ---- IImmiittaattiioonn iiss tthhee NNoott tthhee HHiigghheesstt FFoorrmm ooff FFllaatttteerryy The final category of makefile is the one where every com- mand requires input, the dependencies are incompletely spec- ified, or you simply cannot create more than one target at a time, as mentioned earlier. In addition, you may not have the time or desire to upgrade the makefile to run smoothly with PMake. If you are the conservative sort, this is the compatibility mode for you. It is entered either by giving PMake the --MM flag (for Make), or by executing PMake as ``make..'''' IInn eeiitthheerr ccaassee,, PPMMaakkee ppeerrffoorrmmss tthhiinnggss eexxaaccttllyy lliikkee MMaakkee ((wwhhiillee ssttiillll ssuuppppoorrttiinngg mmoosstt ooff tthhee nniiccee nneeww ffeeaa-- ttuurreess PPMMaakkee pprroovviiddeess)).. TThhiiss iinncclluuddeess:: +o No parallel execution. +o Targets are made in the exact order specified by the make- file. The sources for each target are made in strict left- to-right order, etc. +o A single Bourne shell is used to execute each command, thus the shell's $$ variable is useless, changing directo- ries doesn't work across command lines, etc. +o If no special characters exist in a command line, PMake will break the command into words itself and execute the command directly, without executing a shell first. The characters that cause PMake to execute a shell are: #, =, |, ^, (, ), {, }, ;, &, <, >, *, ?, [, ], :, $, `, and \. You should notice that these are all the characters that are given special meaning by the shell (except ' and , which PMake deals with all by its lonesome). +o The use of the null suffix is turned off. 44..66.. TThhee WWaayy TThhiinnggss WWoorrkk When PMake reads the makefile, it parses sources and targets into nodes in a graph. The graph is directed only in the sense that PMake knows which way is up. Each node contains not only links to all its parents and children (the nodes that depend on it and those on which it depends, respec- tively), but also a count of the number of its children that have already been processed. The most important thing to know about how PMake uses this graph is that the traversal is breadth-first and occurs in two passes. After PMake has parsed the makefile, it begins with the nodes the user has told it to make (either on the command PMake -- A Tutorial PSD:12-45 line, or via a .MAIN target, or by the target being the first in the file not labeled with the .NOTMAIN attribute) placed in a queue. It continues to take the node off the front of the queue, mark it as something that needs to be made, pass the node to Suff_FindDeps (mentioned earlier) to find any implicit sources for the node, and place all the node's children that have yet to be marked at the end of the queue. If any of the children is a .USE rule, its attributes are applied to the parent, then its commands are appended to the parent's list of commands and its children are linked to its parent. The parent's unmade children counter is then decremented (since the .USE node has been processed). You will note that this allows a .USE node to have children that are .USE nodes and the rules will be applied in sequence. If the node has no children, it is placed at the end of another queue to be examined in the second pass. This pro- cess continues until the first queue is empty. At this point, all the leaves of the graph are in the exami- nation queue. PMake removes the node at the head of the queue and sees if it is out-of-date. If it is, it is passed to a function that will execute the commands for the node asynchronously. When the commands have completed, all the node's parents have their unmade children counter decre- mented and, if the counter is then 0, they are placed on the examination queue. Likewise, if the node is up-to-date. Only those parents that were marked on the downward pass are pro- cessed in this way. Thus PMake traverses the graph back up to the nodes the user instructed it to create. When the examination queue is empty and no shells are running to cre- ate a target, PMake is finished. Once all targets have been processed, PMake executes the commands attached to the .END target, either explicitly or through the use of an ellipsis in a shell script. If there were no errors during the entire process but there are still some targets unmade (PMake keeps a running count of how many targets are left to be made), there is a cycle in the graph. PMake does a depth-first traversal of the graph to find all the targets that weren't made and prints them out one by one. 55.. AAnnsswweerrss ttoo EExxeerrcciisseess (3.1) This is something of a trick question, for which I apologize. The trick comes from the UNIX definition of a suffix, which PMake doesn't necessarily share. You will have noticed that all the suffixes used in this tutorial (and in UNIX in general) begin with a period (.ms,, ..cc, etc.). Now, PMake's idea of a suffix is more like English's: it's the characters at the end of a word. With this in mind, one possible solution to this problem goes as follows: PSD:12-46 PMake -- A Tutorial .SUFFIXES : ec.exe .exe ec.obj .obj .asm ec.objec.exe .obj.exe : link -o $(.TARGET) $(.IMPSRC) .asmec.obj : asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC) .asm.obj : asm -o $(.TARGET) $(.IMPSRC) (3.2) The trick to this one lies in the ``:='' variable- assignment operator and the ``:S'' variable-expansion modifier. Basically what you want is to take the pointer variable, so to speak, and transform it into an invocation of the variable at which it points. You might try something like $(PTR:S/^/\$(/:S/$/)) which places ``$('' at the front of the variable name and ``)'' at the end, thus transforming ``VAR,'' for example, into ``$(VAR),'' which is just what we want. Unfortunately (as you know if you've tried it), since, as it says in the hint, PMake does no further substitu- tion on the result of a modified expansion, that's _a_l_l you get. The solution is to make use of ``:='' to place that string into yet another variable, then invoke the other variable directly: *PTR := $(PTR:S/^/\$(/:S/$/)/) You can then use ``$(*PTR)'' to your heart's content. 66.. GGlloossssaarryy ooff JJaarrggoonn aattttrriibbuuttee:: A property given to a target that causes PMake to treat it differently. ccoommmmaanndd ssccrriipptt:: The lines immediately following a dependency line that specify commands to execute to create each of the targets on the dependency line. Each line in the command script must begin with a tab. ccoommmmaanndd--lliinnee vvaarriiaabbllee:: A variable defined in an argument when PMake is first executed. Overrides all assign- ments to the same variable name in the makefile. ccoonnddiittiioonnaall:: A construct much like that used in C that allows a makefile to be configured on the fly based on the local environment, or on what is being made by that invocation of PMake. ccrreeaattiioonn ssccrriipptt:: Commands used to create a target. See ``command script.'' ddeeppeennddeennccyy:: The relationship between a source and a target. This comes in three flavors, as indicated by the opera- tor between the target and the source. `:' gives a straight time-wise dependency (if the target is older than the source, the target is out-of-date), while `!' provides simply an ordering and always considers the PMake -- A Tutorial PSD:12-47 target out-of-date. `::' is much like `:', save it cre- ates multiple instances of a target each of which depends on its own list of sources. ddyynnaammiicc ssoouurrccee:: This refers to a source that has a local variable invocation in it. It allows a single depen- dency line to specify a different source for each tar- get on the line. gglloobbaall vvaarriiaabbllee:: Any variable defined in a makefile. Takes precedence over variables defined in the environment, but not over command-line or local variables. iinnppuutt ggrraapphh:: What PMake constructs from a makefile. Consists of nodes made of the targets in the makefile, and the links between them (the dependencies). The links are directed (from source to target) and there may not be any cycles (loops) in the graph. llooccaall vvaarriiaabbllee:: A variable defined by PMake visible only in a target's shell script. There are seven local vari- ables, not all of which are defined for every target: .TARGET,, ..AALLLLSSRRCC, .OODATE,, ..PPRREEFFIIXX, .IMPSRC,, ..AARRCCHHIIVVEE, and .MEMBER.. ..TTAARRGGEETT, .PREFIX,, ..AARRCCHHIIVVEE, and .MEMBER mmaayy bbee uusseedd oonn ddeeppeennddeennccyy lliinneess ttoo ccrreeaattee ````ddyynnaammiicc ssoouurrcceess..'''' mmaakkeeffiillee:: A file that describes how a system is built. If you don't know what it is after reading this tutorial.... mmooddiiffiieerr:: A letter, following a colon, used to alter how a variable is expanded. It has no effect on the variable itself. ooppeerraattoorr:: What separates a source from a target (on a depen- dency line) and specifies the relationship between the two. There are three: `:'',, ``::::', and `!''.. sseeaarrcchh ppaatthh:: A list of directories in which a file should be sought. PMake's view of the contents of directories in a search path does not change once the makefile has been read. A file is sought on a search path only if it is exclusively a source. sshheellll:: A program to which commands are passed in order to create targets. ssoouurrccee:: Anything to the right of an operator on a dependency line. Targets on the dependency line are usually cre- ated from the sources. ssppeecciiaall ttaarrggeett:: A target that causes PMake to do special things when it's encountered. ssuuffffiixx:: The tail end of a file name. Usually begins with a period, .c oorr ..mmss, e.g. ttaarrggeett:: A word to the left of the operator on a dependency line. More generally, any file that PMake might create. A file may be (and often is) both a target and a source (what it is depends on how PMake is looking at it at the time -- sort of like the wave/particle duality of light, you know). ttrraannssffoorrmmaattiioonn rruullee:: A special construct in a makefile that specifies how to create a file of one type from a file of another, as indicated by their suffixes. PSD:12-48 PMake -- A Tutorial vvaarriiaabbllee eexxppaannssiioonn:: The process of substituting the value of a variable for a reference to it. Expansion may be altered by means of modifiers. vvaarriiaabbllee:: A place in which to store text that may be retrieved later. Also used to define the local environ- ment. Conditionals exist that test whether a variable is defined or not. PMake -- A Tutorial PSD:12-49 TTaabbllee ooff CCoonntteennttss 1. Introduction . . . . . . . . . . . . . . . . 1 2. The Basics of PMake . . . . . . . . . . . . . 2 2.1. Dependency Lines . . . . . . . . . . . . . . 2 2.2. Shell Commands . . . . . . . . . . . . . . . 4 2.3. Variables . . . . . . . . . . . . . . . . . . 6 2.3.1.Local Variables . . . . . . . . . . . . . . . 8 2.3.2.Command-line Variables . . . . . . . . . . . 9 2.3.3.Global Variables . . . . . . . . . . . . . . 9 2.3.4.Environment Variables . . . . . . . . . . . . 10 2.4. Comments . . . . . . . . . . . . . . . . . . 10 2.5. Parallelism . . . . . . . . . . . . . . . . . 10 2.6. Writing and Debugging a Makefile . . . . . . 11 2.7. Invoking PMake . . . . . . . . . . . . . . . 14 2.8. Summary . . . . . . . . . . . . . . . . . . . 18 2.9. Exercises . . . . . . . . . . . . . . . . . . 18 3. Short-cuts and Other Nice Things . . . . . . 19 3.1. Transformation Rules . . . . . . . . . . . . 19 3.2. Including Other Makefiles . . . . . . . . . . 23 3.3. Saving Commands . . . . . . . . . . . . . . . 24 3.4. Target Attributes . . . . . . . . . . . . . . 25 3.5. Special Targets . . . . . . . . . . . . . . . 29 3.6. Modifying Variable Expansion . . . . . . . . 32 3.7. More on Debugging . . . . . . . . . . . . . . 33 3.8. More Exercises . . . . . . . . . . . . . . . 33 4. PMake for Gods . . . . . . . . . . . . . . . 34 4.1. Search Paths . . . . . . . . . . . . . . . . 34 4.2. Archives and Libraries . . . . . . . . . . . 35 4.3. On the Condition... . . . . . . . . . . . . 37 4.4. A Shell is a Shell is a Shell . . . . . . . . 40 4.5. Compatibility . . . . . . . . . . . . . . . . 43 4.5.1.DEFCON 3 -- Variable Expansion . . . . . . . 43 4.5.2.DEFCON 2 -- The Number of the Beast . . . . . 43 4.5.3.DEFCON 1 -- Imitation is the Not the Highest Form of Flattery . . . . . . . . . . . . . . . . . 44 4.6. The Way Things Work . . . . . . . . . . . . . 44 5. Answers to Exercises . . . . . . . . . . . . 45 6. Glossary of Jargon . . . . . . . . . . . . . 46