Note that the update methods, which are expressed in shell, are not entirely portable when we consider deviant platforms such as Windows NT. To that end, we'll go over some practices which can make porting Makefiles a bit easier later on.
The really big picture is that make is a language in which we can express dependencies via a set of dependency rules. These dependency rules are then used to build a dependency graph. (If you distill the vocabulary a bit, you can see that the rules are really adjacency lists and that the dependency graph is no more than a run-of-the-mill directed graph.) Given the graph, make traverses the subgraph whose root is specified on the command line depth first. For example,
% make subgraph1
If a dependency root is not specified then make traverses the subgraph whose root is the first rule in the Makefile.
That's all fine, but without a rule to use to evaluate dependencies, the dependencies have no meaning. make's rule is to evaluate dependencies based on time. If any descendents of a node are younger than the node itself then the node is out of date and must be updated.
If you are using make to help maintain a program, this means that a program module is out of date if any of its constituent modules are newer than the module itself. We will see an example of this shortly.
Syntactically correct make rules will look like this:
Please note that make pays close attention to those tokens which we usually take for granted in our no-longer-punched-card world such as newlines and tabs. If you get too creative with the formatting of your Makefile, make will mysteriously cease to do the expected! Knowing that, let's analyze the make rule constituents:
The first line of a make rule consists of a list of targets, a colon, a list of dependencies, and is terminated with a newline.
| targets ... | The targets are dependent upon the dependencies. Quite often, we will see only one target followed by a long list of dependencies. | REQUIRED |
| dependencies ... | The dependencies should be viewed as components of the targets. | optional |
| update method | The update method is a shell script, OFFSET BY A TAB, which contains the commands necessary to update the target if the target is found to be out of date. | optional |
If you don't know what's going on yet, don't stress! I'm about to explain what's going on. But first, let's see a simple real-world example.
| Rule # | Rule | English |
|---|---|---|
| 1 | program: main.o $(CC) $@ $(LDFLAGS) $^ |
program depends on main.o. When main.o is up to date,
use the statement $(CC) $@ $(LDFLAGS) $^to bring program up to date. |
| 2 | main.o: main.c main.h $(CC) -c $@ $^ |
main.o depends on main.c and main.h |
The simple Makefile in Figure 3 is a small example of how all Makefiles look and work. Let's explain the rules which were used in Figure 3.
Rule 1 is special for a number of reasons. First, it is the first rule. That is important because the first rule is the default rule. More on that later. Second, Rule 1 is the link rule, where we take a bunch of .o files and smash them into one executable binary. That means that we need to write a special update method for the rule which contains the commands necessary to build program from its constituent object files, namely main.o. We accomplish this using an amalgam of obscure syntax and variables. We do that because we want to make it impossible for other people to decipher how to compile our programs.
Rule 2 is a typical make rule. It says that "If either main.c or main.h is newer than main.o then main.o is out of date. Update main.o by executing $(CC) -c $@ $^".
make variables are often a sore point, but they need not be. True, their syntax is abhorrent and it is impossible to remember what the single-character variables mean, but they really are pretty useful.
There are a couple of things to keep in mind. First, all exported shell environment variables are accessible in make. Second, most make variables must be referenced using $(variable) instead of the typical $variable which we use in shell. Sometimes you will see ${variable} used, but I think it's sort of ugly.
Single-character variables are special. In Figure 3, we saw $@ and $^. $@ refers to the rule target and $^ refers to the rule's first dependency. $< which we will talk about later, refers to all depedencies. There are others, of course, but we won't talk about them here.
If an update method needs to refer to a shell variable, such as in the following contrived example (Figure 4), then the $ must be escaped with a $, making the variable reference look like this: $$variable.
target: dependency # When we update this target we need to mail a bunch of people # about it. for i in jim chuck joe; do \ echo "$@ updated" | mail $$i -s "$@ updated"; \ done
The following is an example of a Makefile which you might see in the small projects typical of undergraduate computer science courses.
LIBDIR = . LDFLAGS = -L$(LIBDIR) -lcommonlib CC = /usr/local/bin/gcc CFLAGS = -c -g -Wall project: module1 module2 module1: server server_test server: server.o commonlib $(CC) $@ $^ $(LDFLAGS) server_test: server_test.o server $(CC) $@ $^ $(LDFLAGS) module2: client client: parser.o lexer.o interface.o commonlib $(CC) $@ $(<:commonlib=) $(LDFLAGS) commonlib: libcommonlib.a libcommonlib.a(textio.o): textio.h clean: rm -f *.a *.o
There are only a couple of rules worth talking about in Figure 5.
The first is the client rule. It builds a binary named client out of three object files and a library dependency. The funny syntax, $(<:commonlib=) is a substitution. It takes care of some conflicting needs.
Normally, we would have used the $^ automatic variable to build a binary using its associated object file. Unfortunately that only works with absurdly small projects consisting of but one object file. Here, we need to find some syntax which allows us to tell make that client depends not only on its object files but also on a library module, which we have represented as a (non-terminal) dependency. So we do that on the first line. Our dependencies are all set up.
Now comes the tricky part. We need to somehow write an update method which will link client with the first three listed dependencies, namely client's object files, but exclude the last dependency: the library. (We want to exclude the library because presumably we have stuck the -lcommmonlib in the LDFLAGS variable. This, among other reasons.) So we'll use some make syntax to get us out of the jam.
Let's pick apart $(<:commonlib=). First, the $() notation should be familiar. We're referencing a variable. Second, the < is the variable we are referencing, namely the "all dependencies" variable. Finally, we're left with :commonlib=. That is a string substitution. It means, "While expanding this variable, replace all occurences of the sequence of characters commonlib with the empty sequence of characters." So we've successfully done away with the library issue.
As usual, there were other ways to skin this cat. We could have used a shell escape and filtered commonlib away with sed, awk, perl, or your favorite shell tool. We could have used a make subsequence operator...if our version of make supports such things. There are certainly other ways too.
The second rule I'd like to point out is the funny looking one: libcommonlib.a(textio.o): textio.h. This is THE MOST USEFUL THING YOU CAN LEARN AS A BUILD ENGINEER. I've seen it screwed up in many places, causing lots of frustration to the Makefile users.
The parenthetical notation means that inside the archive libcommonlib.a is a file textio.o. That archive member depends on textio.h. Therefore, when textio.h, the archive member must be updated.
You might think this is pretty uncessary, because we could have done away with the whole problem using rules similar to the following:
textio.o: textio.h $(CC) -c $@ $^ libcommonlib.a: textio.o
Actually, that is probably correct. Not quite as tidy as what we saw in Figure 5, but this is probably okay to do. I don't know, I never do it that way. Usually when I get lazy, I do it the wrong way, which we'll see in Figure 7.
textio.o: textio.h $(CC) -c $@ $^; \ $(AR) rcv libcommonlib.a $@
This is wrong because the transaction of building the object file and updating its container library is not preserved. If a user were to hit ^C between the compile and archive lines in the update method specified in Figure 7, then the object file would be up to date while the archive would be out of date. The archive, however, would never be updated, because there is no rule which targets the library.
In general, there are a few hints which should be observed regarding update methods, particularly when they are written in shell. make is very specific and a bit wierd in the way it processes update methods.
Each line of an update method is executed in a new shell unless the newline is escaped with a backslash. This is important to note for performance reasons as well as parallelization. To demonstrate:
target: object.o $(CC) $@ $^ $(LDFLAGS) echo Finished $@.
In Figure 8, both lines of the update method would be executed in separate shells. This is admitedly a trivial example, but the differences would become apparent immediately if any shell scripting were done in earnest as part of an update method.
target: object.o $(CC) $@ $^ $(LDFLAGS); echo Finished $@.
The following is also acceptable:
target: object.o $(CC) $@ $^ $(LDFLAGS); \ echo Finished $@.
Improper update methods will lead to funny, non-deterministic errors if make is ever used to run parellel build jobs. It is pretty tough to write a parallelizable Makefile correctly, but painstaking attention to the little details of backslashifying newlines can save a lot of trouble.
There are many useful make operators which we have not talked about. Among them are string manipulation functions, subsequence operators, etc. There is some useful subshell syntax which is worth looking at, in the form of $(shell ...).
Many of the make variables, automatic and otherwise, have been left out in our cursory discussion. MAKEFLAGS is a particularly useful one.
Most important, however, we have skipped the entire discussion of implicit rules, their formulation, specification, and utilization. Implicit rules can be your friends when used properly, or your most confounding enemies when used improperly.