Makefile: Difference between revisions

Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
Line 2: Line 2:
Setting up a simple Makefile is easy. You can even go a long way ''without'' a Makefile, simply by using make's internal rulesets, however setting up a global project Makefile that works as intended is quite a bit harder.
Setting up a simple Makefile is easy. You can even go a long way ''without'' a Makefile, simply by using make's internal rulesets, however 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. I don't boast that the [http://sf.net/projects/pdclib PDCLib] Makefile is exceptionally good, but I have seen a lot worse, and would like to share some of the "tricks" I used there that are not that obvious to the 'make' beginner. It is always beneficial to have an actual example you can toy around with until you understood what is being done, so I use the actual PDCLib Makefile here instead of trying to abstract the things I did.
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 cover mainly the [http://sf.net/projects/pdclib PDCLib] Makefile, and show some of the "tricks" that may not be that obvious to the 'make' beginner.


The Makefile creates only one project-wide object file, but it should be easy to expand it for multiple binaries / libraries. I edited out some project-specific stuff (regression testing, for one) to keep it simple, so don't be surprised when you find the actual PDCLib Makefile to be a bit more complex.
The Makefile creates only one project-wide object file, but it should be easy to expand it for multiple binaries/libraries.

== File Lists ==
It is best practice to name you makefile <code>makefile</code> (without an extension) because when ''make'' is executed without any parameters, it will look for a file named <code>makefile</code> in the current directory.


==File Lists==
First, I assemble various "file lists" which I need later in the Makefile.
First, I assemble various "file lists" which I need later in the Makefile.


===Non-Source Files===
=== Non-Source Files ===
<pre>
<pre>
# This is a list of all non-source files that are part of the distribution.
# This is a list of all non-source files that are part of the distribution.
AUXFILES := Makefile Readme.txt
AUXFILES := Makefile Readme.txt
</pre>
</pre>
Further down I have a target ''"dist"'', which packages all source and header files into a tarball. I create lists of the sources and headers anyway, but for auxiliary files which are not used anywhere else in the Makefile, I need this explicit list to have them included in the tarball.
Further down I have a target ''"dist"'', which packages all source and header files into a tarball. I create lists of the sources and headers anyway, but for auxiliary files which are not used anywhere else in the Makefile, I need this explicit list to have them included in the tarball.


===Project Directories===
=== Project Directories ===
<pre>
<pre>
PROJDIRS := functions includes internals
PROJDIRS := functions includes internals
Line 24: Line 26:
''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. Google for a very good paper on the subject titled "recursive make considered harmful"; not only is a single Makefile easier to maintain (once you learned a couple of tricks), it's also more efficient!''
''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. Google for a very good paper on the subject titled "recursive make considered harmful"; not only is a single Makefile easier to maintain (once you learned a couple of tricks), it's also more efficient!''


===Recursion===
=== Recursion ===
<pre>
<pre>
SRCFILES := $(shell find $(PROJDIRS) -mindepth 1 -maxdepth 3 -name "*.c")
SRCFILES := $(shell find $(PROJDIRS) -mindepth 1 -maxdepth 3 -name "*.c")
Line 33: Line 35:
''The -mindepth option keeps any top-level files out of the result set, like tmp.c or test.c or whatever you might have created for testing something on the fly. The -maxdepth option stops the 'find' to recurse into the CVS/ directories I used to have. You might want to adapt this to your requirements.''
''The -mindepth option keeps any top-level files out of the result set, like tmp.c or test.c or whatever you might have created for testing something on the fly. The -maxdepth option stops the 'find' to recurse into the CVS/ directories I used to have. You might want to adapt this to your requirements.''


===Automated Testing, pt. 1===
=== Automated Testing, pt. 1 ===
I think it's time I explain my approach to testing. I write strictly one library function per source file. Then, I add a test driver to that source file:
I think it's time I explain my approach to testing. I write strictly one library function per source file. Then, I add a test driver to that source file:
<pre>
<pre>
Line 46: Line 48:
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).
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===
=== Object Files and Test Driver Executables ===
<pre>
<pre>
OBJFILES := $(patsubst %.c,%.o,$(SRCFILES))
OBJFILES := $(patsubst %.c,%.o,$(SRCFILES))
Line 53: Line 55:
''OBJFILES'' should be clear - a list of the source files, with ''*.c'' exchanged for ''*.o''. ''TSTFILES'' does the same for the extension ''*.t'', which I chose for my test driver executables.
''OBJFILES'' should be clear - a list of the source files, with ''*.c'' exchanged for ''*.o''. ''TSTFILES'' does the same for the extension ''*.t'', which I chose for my test driver executables.


===Dependencies, pt. 1===
=== Dependencies, pt. 1 ===
Many people edit their Makefile every time they add/change an ''#include'' somewhere in their code. Did you know that ''GCC'' can do this automatically for you? Yes, it can. Trust me. Although the approach looks a little backward. For each source file, ''GCC'' will create a ''dependency file'' (which I made end in ''*.d''), which contains a Makefile dependency rule listing that source file's includes. (And more, but see below.)
Many people edit their Makefile every time they add/change an ''#include'' somewhere in their code. Did you know that ''GCC'' can do this automatically for you? Yes, it can. Trust me. Although the approach looks a little backward. For each source file, ''GCC'' will create a ''dependency file'' (which I made end in ''*.d''), which contains a Makefile dependency rule listing that source file's includes. (And more, but see below.)
<pre>
<pre>
Line 59: Line 61:
</pre>
</pre>


===Distribution Files===
=== Distribution Files ===
The last list is the one with all sources, headers, and auxiliary files that should end up in a distribution tarball.
The last list is the one with all sources, headers, and auxiliary files that should end up in a distribution tarball.
<pre>
<pre>
Line 65: Line 67:
</pre>
</pre>


==Phony==
== Phony ==
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:
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:
<pre>
<pre>
Line 71: Line 73:
</pre>
</pre>


==CFLAGS==
== CFLAGS ==
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. ;)
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>
<pre>
Line 81: Line 83:
I suggest you add them one by one instead of all at once. ;) These flags are for C, there are more to be used for C++.
I suggest you add them one by one instead of all at once. ;) These flags are for C, there are more to be used for C++.


==Rules==
== Rules ==


Now come the rules, in their typical backward manner (top-level rule first).
Now come the rules, in their typical backward manner (top-level rule first).


===Top-Level Targets===
=== Top-Level Targets ===
<pre>
<pre>
all: pdclib.a
all: pdclib.a
Line 102: Line 104:
The awkward looking loop in the ''clean'' rule is to avoid confusing error messages if the files to be deleted don't exist.
The awkward looking loop in the ''clean'' rule is to avoid confusing error messages if the files to be deleted don't exist.


===Automated Testing, pt. 2===
=== Automated Testing, pt. 2 ===
<pre>
<pre>
test: testdrivers
test: testdrivers
Line 114: Line 116:
If you get a ''SEGFAULT'' or something like that, add a temporary ''echo $$file'' to the loop to get the name of the test driver this happens in. For ''normal'' test failures, add a diagnostic to the test driver.
If you get a ''SEGFAULT'' or something like that, add a temporary ''echo $$file'' to the loop to get the name of the test driver this happens in. For ''normal'' test failures, add a diagnostic to the test driver.


===Dependencies, pt. 2===
=== Dependencies, pt. 2 ===
<pre>
<pre>
-include $(DEPFILES)
-include $(DEPFILES)
Line 127: Line 129:
This will ''grep'' all those ''TODO'' and ''FIXME'' comments from the files and display them in the terminal. It's nice to be remembered of what's still missing before you do a release. To add another keyword, just add another <code>-e keyword</code>. Don't forget to add <code>todolist</code> to your <code>.PHONY</code> list.
This will ''grep'' all those ''TODO'' and ''FIXME'' comments from the files and display them in the terminal. It's nice to be remembered of what's still missing before you do a release. To add another keyword, just add another <code>-e keyword</code>. Don't forget to add <code>todolist</code> to your <code>.PHONY</code> list.


===Dependencies, pt. 3===
=== Dependencies, pt. 3 ===


Now comes the dependency magic I talked about earlier:
Now comes the dependency magic I talked about earlier:
Line 142: Line 144:
Note that the dependency list of the rule includes the Makefile itself. If you changed e.g. the ''CFLAGS'', you want them to be applied, don't you?
Note that the dependency list of the rule includes the Makefile itself. If you changed e.g. the ''CFLAGS'', you want them to be applied, don't you?


===The Rest===
=== The Rest ===
<pre>
<pre>
%.t: %.c Makefile pdclib.a
%.t: %.c Makefile pdclib.a
Line 149: Line 151:
This is somewhat of an anticlimax in its "triviality". My test drivers need to link against the ''PDCLib'' itself, but that's already all. This is not really perfect; if ''any'' PDCLib function was touched (and, thus, pdclib.a updated), it recreates ''all'' test drivers, even when not necessary. Ah well, no Makefile ever is perfect, but I'd rather have too much compiled than missing a dependency.
This is somewhat of an anticlimax in its "triviality". My test drivers need to link against the ''PDCLib'' itself, but that's already all. This is not really perfect; if ''any'' PDCLib function was touched (and, thus, pdclib.a updated), it recreates ''all'' test drivers, even when not necessary. Ah well, no Makefile ever is perfect, but I'd rather have too much compiled than missing a dependency.


==A Final Word==
== A Final Word ==


Hope this helps you in creating your own Makefile, and then forgetting about it (as you should, because 'make' should lessen your workload, not add more).
Hope this helps you in creating your own Makefile, and then forgetting about it (as you should, because 'make' should lessen your workload, not add more).