Makefile: Difference between revisions
Sometimes, "later" ''does'' come. I said "will continue tonight" and finished over 7 months later, but here it is. ;-)
[unchecked revision] | [unchecked revision] |
(Sometimes, "later" ''does'' come. I said "will continue tonight" and finished over 7 months later, but here it is. ;-)) |
|||
Line 10:
But 'make' requires a well-written Makefile to do these things efficiently and reliably.
= Makefile Tutorial =▼
Setting up a simple Makefile is easy. You can even go a long way ''without'' a Makefile, simply by using make's internal rulesets. Setting up a global project Makefile that works as intended is quite a bit harder.▼
The 'make' manual will tell you about the hundreds of things you can do with a Makefile, but it doesn't give you an example for a ''good'' Makefile. The following examples mainly use the [http://pdclib.rootdirectory.de/trac.fcgi/browser/trunk/Makefile PDCLib] Makefile as a template, and show some of the "tricks" used therein that may not be that obvious to the make beginner. It aims at the GNU variant of make, and uses some expressions specific to that particular make implementation (for example the patsubst function). Adaption to any special needs you might have should not be too hard once you got the idea, however.▼
The Makefile creates only one project-wide linker library, but it should be easy to expand it for multiple binaries/libraries.▼
== Basics ==
Line 36 ⟶ 28:
</pre>
Both ''dependency'' and ''command'' are optional. There might be more than one ''command'' line, in which case they are executed in sequence.
Note that the command ''must be tab-indented''. If your editor environment is set to replace tabs with spaces, you have to undo that setting while editing a makefile.▼
▲Note that
What makes makefiles so hard to read, for the beginner, is that we are not looking at an imperative program here that is executed top-down; 'make' reads the ''whole'' makefile, building a dependency tree, and then resolves the dependencies by hopping from rule to rule until the target you gave it on the command line has successfully been resolved.▼
▲What makes makefiles so hard to read, for the beginner, is that
I won't go into further details. This is not a man page, but a tutorial, so I will show you how a makefile is built, and the ideas behind each line.▼
▲
▲= Makefile Tutorial =
▲Setting up a simple Makefile is easy. You can even go a long way ''without'' a Makefile, simply by using make's internal rulesets. Setting up a global project Makefile that works as intended is quite a bit harder.
▲The 'make' manual will tell you about the hundreds of things you can do with a Makefile, but it doesn't give you an example for a ''good'' Makefile. The following examples mainly use the [http://pdclib.rootdirectory.de/trac.fcgi/browser/trunk/Makefile PDCLib] Makefile as a template, and show some of the "tricks" used therein that may not be that obvious to the make beginner. It aims at the GNU variant of make, and uses some expressions specific to that particular make implementation (for example the patsubst function). Adaption to any special needs you might have should not be too hard once you got the idea, however.
▲The Makefile creates only one project-wide linker library, but it should be easy to expand it for multiple binaries/libraries.
As the Makefile presented in this tutorial takes care of automated testing in a special way, this approach will be explained up front, so the related parts of the tutorial will make sense to you.
As stated above, this tutorial is mainly derived from work done on the PDCLib project - which builds a single linker library. In that project, there is strictly one library function per source file. In each such source file, there is a test driver attached for conditional compilation, like this:
<pre>▼
#ifdef TEST▼
int main()▼
{▼
/* Test function code here */▼
return NUMBER_OF_TEST_ERRORS;▼
}▼
#endif▼
</pre>▼
Thus, when
== File Lists ==
First,
=== Non-Source Files ===
Line 53 ⟶ 71:
</pre>
Further down
=== Project Directories ===
Line 59 ⟶ 77:
PROJDIRS := functions includes internals
</pre>
Those are
''Note that this is not a recursive approach; there is no Makefile in those subdirectories. Dependencies are hard enough to get right if you use one Makefile.
=== Recursion ===
<pre>
SRCFILES := $(shell find $(PROJDIRS) -
HDRFILES := $(shell find $(PROJDIRS) -
</pre>
▲=== Automated Testing, pt. 1 ===
▲<pre>
▲#ifdef TEST
▲int main()
▲{
▲ /* Test function code here */
▲ return NUMBER_OF_TEST_ERRORS;
▲}
▲#endif
▲</pre>
▲Thus, when I compile that source with ''gcc -c'', I get an object file with the library function code; when I compile with ''gcc -DTEST'', I get a test driver executable for that function. Returning the number of errors allows me to do a grand total of errors encountered (see below).
=== Object Files and Test Driver Executables ===
<pre>
OBJFILES := $(patsubst %.c,%.o,$(SRCFILES))
TSTFILES := $(patsubst %.c,%
</pre>
''OBJFILES'' should be clear - a list of the source files, with ''*.c'' exchanged for ''*.o''. ''TSTFILES'' does the same for the
:''Note: This tutorial initially used ''*.t'' instead of ''*_t'' here; this, however, kept us from handling dependencies for test driver executables seperately from those for library object files, which was necessary. See next section.''
=== Dependencies, pt. 1 ===
Many people edit their Makefile every time they add/change an ''#include'' somewhere in their code.
We need two seperate sets of dependency files; one for the library objects, and one for the test driver executables (which usually have additional includes, and thus dependencies, not needed for the OBJFILES).
<pre>
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
TSTDEPFILES := $(patsubst %,%.d,$(TSTFILES))
</pre>
Line 104 ⟶ 112:
</pre>
==
The next one can take you by surprise. When you write a rule for ''make clean'', and there happens to be a file named ''clean'' in your working directory, you might be surprised to find that ''make'' does nothing, because the "target" of the rule ''clean'' already exists. To avoid this, declare such rules as ''phony'', i.e. disable the checking for a file of that name. These will be executed every time:
<pre>
.PHONY: all clean dist
</pre>
Line 113 ⟶ 121:
If you thought ''-Wall'' does tell you everything, you'll be in for a rude surprise now. If you don't even use ''-Wall'', shame on you. ;)
<pre>
-Wwrite-strings -Wmissing-prototypes -Wmissing-declarations \
-Wredundant-decls -Wnested-externs -Winline -Wno-long-long \
-Wuninitialized -Wconversion -Wstrict-prototypes
CFLAGS := -g -std=c99 $(WARNINGS)
</pre>
== Rules ==
Now come the rules, in their typical backward manner (top-level rule first). The topmost rule is the default one (if 'make' is called without an explicit target). It is common practice to have the first rule called "all".
=== Top-Level Targets ===
Line 132 ⟶ 141:
clean:
-@$(RM) $(wildcard $(OBJFILES) $(DEPFILES) $(TSTFILES
dist:
@tar czf pdclib.tgz $(ALLFILES)
</pre>
The ''@'' at the beginning of
The ''-'' at the beginning of a line tells ''make'' to continue even in case an error is encountered (default behaviour is to terminate the whole build).
The ''$(RM)'' in the ''clean'' rule is the platform-independent command to remove a file.
The ''$?'' in the ''pdclib.a'' rule is an internal variable, which ''make'' expands to list all dependencies to the rule ''which are newer than the target''.
=== Automated Testing, pt. 2 ===
<pre>
-@rc=0; count=0
for file in $(TSTFILES); do \
count=`expr $$count + 1`; done; echo; echo "Tests executed: $$count Tests failed: $$rc"▼
echo " TST $$file"; ./$$file; \
rc=`expr $$rc + $$?`; count=`expr $$count + 1`; \
done; \
testdrivers: $(TSTFILES)
Line 151 ⟶ 168:
Call it crude, but it works beautifully for me. The leading ''-'' means that 'make' will not abort when encountering an error, but continue with the loop.
=== Dependencies, pt. 2 ===
<pre>
-include $(DEPFILES) $(TSTDEPFILES)
</pre>
Further below, you will see how dependency files are ''generated''. Here, we ''include'' all of them, i.e. make the dependencies listed in them part of our Makefile. Never mind that they might not even exist when we run our Makefile the first time - the leading "-" again suppresses the errors.
=== Extracting TODO Statements ===
Line 164 ⟶ 181:
-@for file in $(ALLFILES); do fgrep -H -e TODO -e FIXME $$file; done; true
</pre>
This will ''grep'' all those ''TODO'' and ''FIXME'' comments from
=== Dependencies, pt. 3 ===
Now comes the dependency magic I talked about earlier
<pre>
%.o: %.c Makefile
@$(CC) $(CFLAGS)
</pre>
Isn't it a beauty? ;-)
The
Compiling the object file actually looks like a side effect. ;-)
Note that the dependency list of the rule includes the Makefile itself. If you changed e.g. the ''CFLAGS'' in the Makefile, you want them to be applied, don't you? Using the $< macro ("first dependency") in the command makes sure we do not attempt to compile the Makefile as C source.
Of course we also need a rule for generating the test driver executables (and their dependency files):
<pre>
%
@$(CC) $(CFLAGS) -
</pre>
Here you can see why test driver executables get a ''*_t'' suffix instead of a ''*.t'' extension: The ''-MMD'' option uses the basename (i.e., filename without extension) of the ''compiled'' file as basis for the dependency file. If we would compile the sources into ''abc.o'' and ''abc.t'', the dependency files would both be named ''abc.d'', overwriting each other.
== Summary ==
A well-written Makefile can make maintaining a code base much easier, as it can wrap complex command sequences into simple 'make' invocations. Especially for large projects, it also cuts back on compilation times when compared to a dumb "build.sh" script. And, once written, modifications to a well-written Makefile are seldom necessary.
= Advanced Techniques =
Some of the stuff we did above already ''was'' pretty advanced, and no mistake. But we needed those features for the basic yet convenient setup. Below you will find some even trickier stuff, which might not be for everybody but is immensely helpful ''if'' you need it.
== Conditional Evaluation ==
Sometimes it
Luckily, 'make' allows for conditional evaluation and manual error reporting, quite akin to the C preprocessor:
Line 205 ⟶ 230:
endif
</pre>
= See Also =
|