Makefile: Difference between revisions

Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content deleted Content added
No edit summary
m Bot: Replace deprecated source tag with syntaxhighlight
Line 31: Line 31:
A rule defines a ''target'', 0..n ''dependencies'', and 0..n ''commands''. The general idea is that ''make'' checks if the target (file) is there; if it isn't, or any of the dependencies is newer than the target, the commands are executed. The general syntax is:
A rule defines a ''target'', 0..n ''dependencies'', and 0..n ''commands''. The general idea is that ''make'' checks if the target (file) is there; if it isn't, or any of the dependencies is newer than the target, the commands are executed. The general syntax is:


<source lang="make">
<syntaxhighlight lang="make">
target: dependency
target: dependency
command
command
</syntaxhighlight>
</source>


Both ''dependency'' and ''command'' are optional. There might be more than one ''command'' line, in which case they are executed in sequence.
Both ''dependency'' and ''command'' are optional. There might be more than one ''command'' line, in which case they are executed in sequence.
Line 49: Line 49:


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:
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:
<source lang="C">
<syntaxhighlight lang="C">
#ifdef TEST
#ifdef TEST
int main()
int main()
Line 57: Line 57:
}
}
#endif
#endif
</syntaxhighlight>
</source>
Thus, when that source is compiled with <code>gcc -c</code>, it results in an object file with the library function code; when compiled with <code>gcc -DTEST</code>, it gives a test driver executable for that function. Returning the number of errors allows to do a grand total of errors encountered (see below).
Thus, when that source is compiled with <code>gcc -c</code>, it results in an object file with the library function code; when compiled with <code>gcc -DTEST</code>, it gives a test driver executable for that function. Returning the number of errors allows to do a grand total of errors encountered (see below).


Line 66: Line 66:
=== Non-Source Files ===
=== Non-Source Files ===


<source lang="make">
<syntaxhighlight lang="make">
# 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
</syntaxhighlight>
</source>


Further down we will have a target ''"dist"'', which packages all required files into a tarball for distribution. Lists of the sources and headers are created anyway. But there are commonly some auxiliary files, which are not referenced anywhere else in the Makefile but should still end up in the tarball. These are listed here.
Further down we will have a target ''"dist"'', which packages all required files into a tarball for distribution. Lists of the sources and headers are created anyway. But there are commonly some auxiliary files, which are not referenced anywhere else in the Makefile but should still end up in the tarball. These are listed here.


=== Project Directories ===
=== Project Directories ===
<source lang="make">
<syntaxhighlight lang="make">
PROJDIRS := functions includes internals
PROJDIRS := functions includes internals
</syntaxhighlight>
</source>
Those are subdirectories holding the actual sources. (Or rather, searched for source files automatically, see below.) These could be subprojects, or whatever. We could simply search for source files starting with the current working directory, but if you like to have temporary subdirectories in your project (for testing, keeping reference sources etc.), that wouldn't work.
Those are subdirectories holding the actual sources. (Or rather, searched for source files automatically, see below.) These could be subprojects, or whatever. We could simply search for source files starting with the current working directory, but if you like to have temporary subdirectories in your project (for testing, keeping reference sources etc.), that wouldn't work.


Line 82: Line 82:


=== Sources and Headers ===
=== Sources and Headers ===
<source lang="make">
<syntaxhighlight lang="make">
SRCFILES := $(shell find $(PROJDIRS) -type f -name "\*.c")
SRCFILES := $(shell find $(PROJDIRS) -type f -name "\*.c")
HDRFILES := $(shell find $(PROJDIRS) -type f -name "\*.h")
HDRFILES := $(shell find $(PROJDIRS) -type f -name "\*.h")
</syntaxhighlight>
</source>
It should be obvious to see what these two lines do. We now have a list of all source and header files in our project directories.
It should be obvious to see what these two lines do. We now have a list of all source and header files in our project directories.


=== Object Files and Test Driver Executables ===
=== Object Files and Test Driver Executables ===
<source lang="make">
<syntaxhighlight lang="make">
OBJFILES := $(patsubst %.c,%.o,$(SRCFILES))
OBJFILES := $(patsubst %.c,%.o,$(SRCFILES))
TSTFILES := $(patsubst %.c,%_t,$(SRCFILES))
TSTFILES := $(patsubst %.c,%_t,$(SRCFILES))
</syntaxhighlight>
</source>
''OBJFILES'' should be clear - a list of the source files, with ''*.c'' exchanged for ''*.o''. ''TSTFILES'' does the same for the filename suffix ''*_t'', which we will use for our test driver executables.
''OBJFILES'' should be clear - a list of the source files, with ''*.c'' exchanged for ''*.o''. ''TSTFILES'' does the same for the filename suffix ''*_t'', which we will use for our test driver executables.


Line 103: Line 103:


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).
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).
<source lang="make">
<syntaxhighlight lang="make">
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
TSTDEPFILES := $(patsubst %,%.d,$(TSTFILES))
TSTDEPFILES := $(patsubst %,%.d,$(TSTFILES))
</syntaxhighlight>
</source>


=== 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.
<source lang="make">
<syntaxhighlight lang="make">
ALLFILES := $(SRCFILES) $(HDRFILES) $(AUXFILES)
ALLFILES := $(SRCFILES) $(HDRFILES) $(AUXFILES)
</syntaxhighlight>
</source>


== .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. These will be executed every time:
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:
<source lang="make">
<syntaxhighlight lang="make">
.PHONY: all clean dist check testdrivers todolist
.PHONY: all clean dist check testdrivers todolist
</syntaxhighlight>
</source>


== 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. ;)
<source lang="make">
<syntaxhighlight lang="make">
WARNINGS := -Wall -Wextra -pedantic -Wshadow -Wpointer-arith -Wcast-align \
WARNINGS := -Wall -Wextra -pedantic -Wshadow -Wpointer-arith -Wcast-align \
-Wwrite-strings -Wmissing-prototypes -Wmissing-declarations \
-Wwrite-strings -Wmissing-prototypes -Wmissing-declarations \
Line 128: Line 128:
-Wconversion -Wstrict-prototypes
-Wconversion -Wstrict-prototypes
CFLAGS := -g -std=gnu99 $(WARNINGS)
CFLAGS := -g -std=gnu99 $(WARNINGS)
</syntaxhighlight>
</source>
It is suggested to add new warning options to your project one at a time instead of all at once, to avoid getting swamped in warnings. ;) These flags are merely recommendations for C work. If you use C++, you need different ones. Check out the GCC manual; each major compiler update changes the list of available warning options.
It is suggested to add new warning options to your project one at a time instead of all at once, to avoid getting swamped in warnings. ;) These flags are merely recommendations for C work. If you use C++, you need different ones. Check out the GCC manual; each major compiler update changes the list of available warning options.


Line 136: Line 136:


=== Top-Level Targets ===
=== Top-Level Targets ===
<source lang="make">
<syntaxhighlight lang="make">
all: pdclib.a
all: pdclib.a


Line 147: Line 147:
dist:
dist:
@tar czf pdclib.tgz $(ALLFILES)
@tar czf pdclib.tgz $(ALLFILES)
</syntaxhighlight>
</source>
The ''@'' at the beginning of a line tells ''make'' to be quiet, i.e. not to echo the executed commands on the console prior to executing them. The Unix credo is "no news is good news". You don't get a list of processed files with ''cp'' or ''tar'', either, so it's completely beyond me why developers chose to have their Makefiles churn out endless lists of useless garbage. One very practical advantage of shutting up ''make'' is that you actually get to ''see'' those compiler warnings, instead of having them drowned out by noise.
The ''@'' at the beginning of a line tells ''make'' to be quiet, i.e. not to echo the executed commands on the console prior to executing them. The Unix credo is "no news is good news". You don't get a list of processed files with ''cp'' or ''tar'', either, so it's completely beyond me why developers chose to have their Makefiles churn out endless lists of useless garbage. One very practical advantage of shutting up ''make'' is that you actually get to ''see'' those compiler warnings, instead of having them drowned out by noise.


Line 157: Line 157:


=== Automated Testing, pt. 2 ===
=== Automated Testing, pt. 2 ===
<source lang="make">
<syntaxhighlight lang="make">
check: testdrivers
check: testdrivers
-@rc=0; count=0; \
-@rc=0; count=0; \
Line 167: Line 167:


testdrivers: $(TSTFILES)
testdrivers: $(TSTFILES)
</syntaxhighlight>
</source>
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.
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.


Line 173: Line 173:


=== Dependencies, pt. 2 ===
=== Dependencies, pt. 2 ===
<source lang="make">
<syntaxhighlight lang="make">
-include $(DEPFILES) $(TSTDEPFILES)
-include $(DEPFILES) $(TSTDEPFILES)
</syntaxhighlight>
</source>
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.
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 ===
=== Extracting TODO Statements ===
<source lang="make">
<syntaxhighlight lang="make">
todolist:
todolist:
-@for file in $(ALLFILES:Makefile=); do fgrep -H -e TODO -e FIXME $$file; done; true
-@for file in $(ALLFILES:Makefile=); do fgrep -H -e TODO -e FIXME $$file; done; true
</syntaxhighlight>
</source>
Taking all files in your project, with exception of the Makefile itself, this will ''grep'' all those ''TODO'' and ''FIXME'' comments from your files, and display them in the terminal. It is nice to be remembered of what is still missing before you do a release. To add another keyword, just insert another <code>-e keyword</code>.
Taking all files in your project, with exception of the Makefile itself, this will ''grep'' all those ''TODO'' and ''FIXME'' comments from your files, and display them in the terminal. It is nice to be remembered of what is still missing before you do a release. To add another keyword, just insert another <code>-e keyword</code>.


Line 219: Line 219:
Below is an example of a ''backup'' target, which creates a dated 7-Zip archive of the directory where the Makefile resides.
Below is an example of a ''backup'' target, which creates a dated 7-Zip archive of the directory where the Makefile resides.


<source lang="make">
<syntaxhighlight lang="make">
THISDIR := $(shell basename `pwd`)
THISDIR := $(shell basename `pwd`)
TODAY := $(shell date +%Y-%m-%d)
TODAY := $(shell date +%Y-%m-%d)
Line 226: Line 226:
backup: clean
backup: clean
@tar cf - ../$(THISDIR) | 7za a -si ../$(BACKUPDIR)/$(THISDIR).$(TODAY)_`date +%H%M`.tar.7z
@tar cf - ../$(THISDIR) | 7za a -si ../$(BACKUPDIR)/$(THISDIR).$(TODAY)_`date +%H%M`.tar.7z
</syntaxhighlight>
</source>


== Summary ==
== Summary ==
Line 242: Line 242:
Luckily, 'make' allows for conditional evaluation and manual error reporting, quite akin to the C preprocessor:
Luckily, 'make' allows for conditional evaluation and manual error reporting, quite akin to the C preprocessor:


<source lang="make">
<syntaxhighlight lang="make">
ifndef FRAMEWORK_PATH
ifndef FRAMEWORK_PATH
$(error FRAMEWORK_PATH is not set. Please set to the path where you installed "Framework".)
$(error FRAMEWORK_PATH is not set. Please set to the path where you installed "Framework".)
Line 250: Line 250:
$(warning FRAMEWORK_PATH is set to $(FRAMEWORK_PATH), not /usr/lib/framework. Are you sure this is correct?)
$(warning FRAMEWORK_PATH is set to $(FRAMEWORK_PATH), not /usr/lib/framework. Are you sure this is correct?)
endif
endif
</syntaxhighlight>
</source>


== Multi-Target ==
== Multi-Target ==