CBE Part 10: Translation Units and GNU Make
Even medium sized projects require that you use more than just one translation unit. This requires to have some understanding how code needs to be organized, how to provide header files. It also requires some understand on how tools like the compiler and linker work. And it also requires to use tools for implementing a build system. IDEs (Integrated development environments) provide easy to use build systems. You click on a button and after some miracle happen your build is complete. Or you get some strange error and have no glue how to fix it if you don't know how such a build system works in principle. In this class we will use GNU Make for implementing our own build system.
Sooner or later you should invest some rainy afternoon in reading the complete GNU Make Documentation. However, it can be of advantage to do that with some background about how GNU Make works in general, what kind of problems it can solves and what kind of features it provides. Giving you some of this background is the purpose of the material provided here.
Provided Material
The Makefile that was developed in the video:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | src := $(wildcard *.c)
obj := $(patsubst %.c,%.o,$(src))
deps := $(patsubst %,%.d,$(src))
a.out: $(obj)
gcc $^
%.o: %.c
gcc -MT $@ -MMD -MF $<.d -c $<
clean:
rm -f a.out $(obj)
-include $(deps)
|
Note that in HTML you do not have tabs in the character set. So if you copy and paste the content of the Makefile you will not have tabs in the Makefile. As you know this is not what you want in a Makefile. So use wget or curl if you want to download the Makefile file directly.
GNU Make Features Used in the Example
Simply Expanded Variables
GNU Make has different flavors of variables. In all cases the value of a variable is just a string. If foo is the identifier of a variable then $(foo) is its value. Here so called simply expanded variables are used. You specify this flavor if you initialize a variable with ':=' (and not with '='). That means in an expression $(foo) immediately gets expanded to the string stored in the variable. Unlike build rules the assignments of variables are processed line by line. After this lines
1 2 3 | foo := x y z
bar := $(foo)
foo := a b c
|
variable foo has the value a b c and bar the value x y z.
Wildcard Function and Text Functions
Using the wildcard function you can get a list of existing files that match a given pattern. For example $(wildcard *.c) gives a list of all files in the current directory that have the suffix '.c' and after
1 | src.c := $(wildcard *.c)
|
this list is the value of variable src.c. Note that in GNU Make a list means that you have a string of whitespace-separated words. So “x y z” is in this sense a list with words “x”, “y” and “z”.
Because all variables are string a rich set of text functions are provided to conveniently manipulate strings. Function patsubstr
1 | $(patsubst pattern,replacement,text)
|
can be used to replace foo.c bar.c with foo.o bar.o as follows:
1 2 | text := foo.c bar.c
replacement := $(patsubst %.c,%.o,text)
|
Here “%.c” was used for the pattern. It matches if a word in the list text has the suffix “.c”. In the replacement the “%” is replaced with the part of the pattern that matched '%' in the pattern.
Pattern Rules and Automatic Variables
It would be tedious to write rules like
1 2 3 4 5 | foo.o : foo.c
gcc -c foo.c
bar.o : bar.c
gcc -c foo.c
|
where you basically repeat the same rule for every source file. More conveniently you can express the same with a pattern rule
1 2 | %.o : %.c
gcc -c $<
|
where “%.c” in the dependency list can match any file with suffix “.c”. For each match a target “%.o” is considered where '%' is replaced with the matched pattern (in this case the base name of a C source file). In the command you can use automatic variables to obtain values for the matched target or match dependency. For example, with “$@” you get the target and with the “$<” name of the first word in the dependency list.
In this example (this is a complete Makefile, so try it out):
1 2 | print: foo bar
echo $<
|
you would see after make print just the word “foo” (because “$<” gives just the first word of the dependecies). To get all dependencies you can use “$^”:
1 2 3 4 | print: foo bar
echo $<
print_all: foo bar
echo $^
|
Now with make print_all you get foo bar.
Including Files
With the include directive you can include the content of another file which than gets processed like a regular part of the makefile (so like when you include files with the C preprocessor). If in this example
1 | include dummy
|
no file dummy exists and no rule was defined to create it, you will get an error. Using instead -include prevents such an error in this case, the include then just gets ignored.
Experiments with the Makefile
Here the source files from the video:
1 2 3 4 5 6 7 8 | #ifndef BAR_H
#define BAR_H
#include "foo.h"
void bar(int a);
#endif // BAR_H
|
1 2 3 4 5 6 7 8 | #include <stdio.h>
#include "bar.h"
void
bar(int a)
{
puts("bar");
}
|
1 2 3 4 5 6 | #ifndef FOO_H
#define FOO_H
void foo();
#endif // FOO_H
|
1 2 3 4 5 6 7 8 9 10 | #include <stdio.h>
#include "bar.h"
#include "foo.h"
void
foo()
{
puts("foo");
bar(42);
}
|
1 2 3 4 5 6 7 | #include "foo.h"
int
main(void)
{
foo();
}
|
And here the dependency graph that we have to consider in the build system:
Test the makefile by “touching” certain files and then use make to rebuild:
theon$ make gcc -MT main.o -MMD -MF main.c.d -c main.c gcc bar.o foo.o main.o theon$ touch bar.h theon$ make gcc -MT bar.o -MMD -MF bar.c.d -c bar.c gcc -MT foo.o -MMD -MF foo.c.d -c foo.c gcc bar.o foo.o main.o theon$ touch bar.c theon$ make gcc -MT bar.o -MMD -MF bar.c.d -c bar.c gcc bar.o foo.o main.o theon$ touch main.c theon$
Advantage of the Current Makefile
The following can be done without modifying the makefile:
-
Adding or removing include directives. Also header files can include other header files. The build system will keep track on how the dependencies have changed.
-
You also can add new source files to the project as long as these do not contain a main() function. Currently we only support the generation of one executable (see Disadvantages and How to Fix Them below).
Try things on your own, for example: Add a source file dummy.c that includes dummy.h. Let foo.h include bar.h and let bar.h include dummy.h etc.
Disadvantages and How to Fix Them
We certainly want to give the executable a name different from a.out. If we think of the executable as a test program for functions implemented in the other source files we probably would like to have more than just one test program. For every bug that we find in the implementation we want to add a new test so that we don't run into the same problem again.
Many build system used in IDEs have problems when you delete or rename files in your projects. You haven find tips in the internet like “just create a new project, import the files you still need and delete the old project”. Currently we certainly can do better than that.
The Clean target
In the makefile developed in the video I simply forgot that make clean should also delete the dependency files. This is easy to fix. Change the rule clean as follows:
1 2 | clean:
rm -f a.out $(obj) $(deps)
|
Even better use the variable RM that GNU make predefines (see variables used in implicit rules) and has the default value rm -f. Note that the option '-f' is important so that you do not get an error if you call make clean twice:
1 2 | clean:
$(RM) a.out $(obj) $(deps)
|
There is actually another problem. Try the following:
-
Run make so that we have complete build.
-
Create a file clean, e.g. with touch clean.
-
Run make clean
In this case make clean will not delete the generated files:
theon$ make gcc -MT bar.o -MMD -MF bar.c.d -c bar.c gcc -MT foo.o -MMD -MF foo.c.d -c foo.c gcc -MT main.o -MMD -MF main.c.d -c main.c gcc bar.o foo.o main.o theon$ touch clean theon$ make clean make: 'clean' is up to date. theon$
For make clean we hoped to take advantage how GNU make handles rules with an empty list of dependencies:
-
If a target has no dependencies the rule get always triggered if the target does not exists
-
However, if the target exists the rule never gets triggered.
Fortunately GNU make supports this hack by declaring clean as a phony target:
1 2 3 | .PHONY: clean
clean:
$(RM) a.out $(obj) $(deps)
|
Now the existence of a file named clean gets ignored and the rule always gets triggered.
Compiler and Compilation Flags
Currently we explicitly compile with gcc. It should be possible to select a different compiler without rewriting the makefile. It also should be possible to specify some compiler flags, e.g. -Wall for getting more warings or -O3 to turn on aggressive optimization.
In variables used in implicit rules you find the following predefined variables that are interesting in this context:
-
CC for selecting a compiler. The default value is cc which is on Unix-like systems the default C compiler. For example, on MacOS it is alink to clang, on Linux to gcc.
-
CFLAGS for compiler flags
-
CPPFLAGS for preprocessor flags
-
LDFLAGS for linker flags
We will use these variables in the rule commands as follows:
1 2 3 4 5 | a.out: $(obj)
$(CC) $^ $(LDFLAGS)
%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) -MT $@ -MMD -MF $<.d $<
|
Here the complete code of the makefile with the changes applied so far:
src := $(wildcard *.c)
obj := $(patsubst %.c,%.o,$(src))
deps := $(patsubst %,%.d,$(src))
a.out: $(obj)
$(CC) $^ $(LDFLAGS)
%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MF $<.d $<
.PHONY: clean
clean:
$(RM) a.out $(obj) $(deps)
-include $(deps)
You now can specify the compiler when invoking make or by defining and exporting a shell variable:
theon$ make CC=gcc CFLAGS="-Wall -O3" gcc -c -Wall -O3 -MT bar.o -MMD -MF bar.c.d bar.c gcc -c -Wall -O3 -MT foo.o -MMD -MF foo.c.d foo.c gcc -c -Wall -O3 -MT main.o -MMD -MF main.c.d main.c gcc bar.o foo.o main.o theon$ make clean rm -f a.out bar.o foo.o main.o bar.c.d foo.c.d main.c.d theon$ export CC=gcc theon$ make gcc -c -MT bar.o -MMD -MF bar.c.d bar.c gcc -c -MT foo.o -MMD -MF foo.c.d foo.c gcc -c -MT main.o -MMD -MF main.c.d main.c gcc bar.o foo.o main.o theon$
Unfortunately on theon this version of the makefile will not work with the default values because cc is not set to a C compiler:
theon$ make clean rm -f a.out bar.o foo.o main.o bar.c.d foo.c.d main.c.d theon$ make cc -c -MT bar.o -MMD -MF bar.c.d bar.c make: cc: No such file or directory make: *** [Makefile:9: bar.o] Error 127 theon$
To deal with platform that do not comply to all the Unix conventions is unfortunate but what you have to expect in the real world. Here some workaround to select gcc in the case no compiler was explicitly selected by the user and the defaul Unix compiler does not exists:
1 2 3 4 5 6 | ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
|
The value of $(origin CC) is default if CC was not set before make was invoked. With \((shell \)(CC) -v > /dev/null 2>&1 && echo “sane”) we try to invoke the default compiler and if that succeeds print “sane”. If we succeed the value of cc_check is “sane”. If this is not the case we set CC to gcc.
Here the patched makefile
ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
src := $(wildcard *.c)
obj := $(patsubst %.c,%.o,$(src))
deps := $(patsubst %,%.d,$(src))
a.out: $(obj)
$(CC) $^ $(LDFLAGS)
%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MF $<.d $<
.PHONY: clean
clean:
$(RM) a.out $(obj) $(deps)
-include $(deps)
and here a test
theon$ make clean rm -f a.out bar.o foo.o main.o bar.c.d foo.c.d main.c.d theon$ make gcc -c -MT bar.o -MMD -MF bar.c.d bar.c gcc -c -MT foo.o -MMD -MF foo.c.d foo.c gcc -c -MT main.o -MMD -MF main.c.d main.c gcc bar.o foo.o main.o theon$
Multiple Executable Test Programs
Often projects have one directory that just contains the source files with a main() functions, i.e. the test programs. And another library directory with all the other source files. We will keep it simple and will just use one directory. But we also want to keep the makefile simple. For achieving that we require that only source files with a prefix xtest contain a main() function.
Clash with Implicit Rules
We can use the filter text function together with patsubst text function to get from the list of lists of targets:
1 2 | # all targets
target := $(filter xtest%,$(patsubst %.c,%,$(src)))
|
Because nesting functions easily looks cluttered I split lines by escaping the newline with a backslash:
1 2 3 4 5 | # all targets
target :=\
$(filter xtest%,\
$(patsubst %.c,%,\
$(src)))
|
We will also need a list of the object files that are required by the target. These are the object file with out the prefix xtest and we get them from the obj variable using the filter-out text function:
1 2 3 4 | # objects that are required by the targets
lib.o :=\
$(filter-out xtest%,\
$(obj))
|
The new default rule should now generate or update all test programs. We therefore define a new rule all that depends on the file list in target and all object files. This target is also a phony target. To be the default rule it has to be the first rule in the makefile. The special variable .DEFAULTGOAL_ one can explicitly specify the default rule:
1 2 3 | .DEFAULT_GOAL := all
.PHONY: all
all: $(target) $(obj)
|
We also change the clean rule to delete the target list instead of a.out.
1 2 3 | .PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps)
|
So far we had only one target a.out and used this rule for building it:
1 2 | a.out: $(obj)
$(CC) $^ $(LDFLAGS)
|
Now we have eventually multiple executables so we need a pattern rule to specify how to generate for example xtest_main from its object file xtest_main.o and all the other object files:
1 2 | %: %.o $(lib.o)
$(CC) -o $@ $^ $(LDFLAGS)
|
So we end up with the following makefile:
#
# patch: If user has not defined CC and default value does not exist use gcc
#
ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
#
# Define list of source files, object files, targets, etc
#
# all source files
src :=\
$(wildcard *.c)
# all object files
obj :=\
$(patsubst %.c,%.o,\
$(src))
# all targets
target :=\
$(filter xtest%,\
$(patsubst %.c,%,\
$(src)))
# objects that are required by the targets
lib.o :=\
$(filter-out xtest%,\
$(obj))
# dependency file that will be generated by compiler
deps :=\
$(patsubst %,%.d,\
$(src))
#
# Build rules
#
.DEFAULT_GOAL := all
.PHONY: all
all: $(target) $(obj)
%: %.o $(lib.o)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MF $<.d $<
.PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps)
#
# Include dependecies (if already generated)
#
-include $(deps)
It just has this little flaw of not working:
theon$ make clean rm -f xtest_main bar.o foo.o xtest_main.o bar.c.d foo.c.d xtest_main.c.d theon$ make gcc -c -MT xtest_main.o -MMD -MF xtest_main.c.d xtest_main.c gcc -c -MT bar.o -MMD -MF bar.c.d bar.c gcc -c -MT foo.o -MMD -MF foo.c.d foo.c gcc -o xtest_main xtest_main.o bar.o foo.o theon$
The problem is that GNU make has predefined implicit rules, or built-in rules, that often makes it easier to write makefiles. In this case it causes this little problem.
With the option -r all predefined implicit rules are canceled. So this would be a quick and dirty fix:
theon$ make clean rm -f xtest_main bar.o foo.o xtest_main.o bar.c.d foo.c.d xtest_main.c.d theon$ make -r gcc -c -MT xtest_main.o -MMD -MF xtest_main.c.d xtest_main.c gcc -c -MT bar.o -MMD -MF bar.c.d bar.c gcc -c -MT foo.o -MMD -MF foo.c.d foo.c gcc -o xtest_main xtest_main.o bar.o foo.o theon$
Of course this is not a nice solution. With make -p you can see all rules that are considered by make. This list will be long. But with
1 | _make -p | less_
|
you will easily find the relevant one:
1 2 3 | %: %.c
# recipe to execute (built-in):
$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@
|
You will also find in the list
1 | LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
|
So this rule states that xtest_main depends on xtest_main.c and that the target can be generated basically with gcc xtestmain.c -o xtest_main~. So this shortcut bypasses our rule that the target depends on all these object files.
Deleting Implicit Rules
As we found out that just one predefined implicit rules is causing the trouble we simply delete it (see Canceling Implicit Rules):
1 | %: %.c
|
So we end up with this makefile
#
# patch: If user has not defined CC and default value does not exist use gcc
#
ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
#
# Define list of source files, object files, targets, etc
#
# all source files
src :=\
$(wildcard *.c)
# all object files
obj :=\
$(patsubst %.c,%.o,\
$(src))
# all targets
target :=\
$(filter xtest%,\
$(patsubst %.c,%,\
$(src)))
# objects that are required by the targets
lib.o :=\
$(filter-out xtest%,\
$(obj))
# dependency file that will be generated by compiler
deps :=\
$(patsubst %,%.d,\
$(src))
#
# Build rules
#
.PHONY: all
all: $(target) $(obj)
# delete implicit rule for building an executable directly from its source file
% : %.c
# our rule: to build target link its object file against library object files
%: %.o $(lib.o)
$(CC) -o $@ $^ $(LDFLAGS)
# our rule to build objects: also generate a dependency file
%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MF $<.d $<
.PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps)
#
# Include dependecies (if already generated)
#
-include $(deps)
And this solves our problem:
theon$ make clean rm -f xtest_main bar.o foo.o xtest_main.o bar.c.d foo.c.d xtest_main.c.d theon$ make gcc -c -MT xtest_main.o -MMD -MF xtest_main.c.d xtest_main.c gcc -c -MT bar.o -MMD -MF bar.c.d bar.c gcc -c -MT foo.o -MMD -MF foo.c.d foo.c gcc -o xtest_main xtest_main.o bar.o foo.o theon$
Deleting Header Files
We now even can add more files to our project and make takes care of everything. Say we add the header dummy.h
1 2 3 4 5 6 | #ifndef DUMMY_H
#define DUMMY_H
void dummy(void);
#endif // DUMMY_H
|
and the source file dummy.c
1 2 3 4 5 6 | #include "dummy.h"
void
dummy(void)
{
}
|
If previously everything was already build just dummy.o get created and the target is created by linking the object files:
theon$ make gcc -o xtest_main xtest_main.o bar.o dummy.o foo.o theon$
So far so good. But then for some reason we just wanna delete the header file dummy.h and not the source file. Afterwards we get an error from make:
theon$ rm dummy.h theon$ make make: *** No rule to make target 'dummy.h', needed by 'dummy.o'. Stop. theon$
The origin of that problem is the dependency file that was generated by the compiler for us:
dummy.o: dummy.c dummy.h
As you can read from the error message the object files dummy.o depends on dummy.h. But the file dummy.h does not exist and there is no rule to build it. So the solution is also found in the error message. The dependency file should be extended by a rule for dummy.h:
1 2 3 | dummy.o: dummy.c dummy.h
dummy.h :
|
Because the rule for dummy.h has no dependencies it never fails (like the rule for clean). Luckily the compiler will generate with the addition option -MP here the relevant part from the gcc man page:
1 2 3 4 5 6 7 8 9 10 | -MP This option instructs CPP to add a phony target for each dependency
other than the main file, causing each to depend on nothing. These
dummy rules work around errors make gives if you remove header
files without updating the Makefile to match.
This is typical output:
test.o: test.c test.h
test.h:
|
So we add this option to the rule for generating object files:
1 2 | %.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MP -MF $<.d $<
|
This is the makefile after the change:
#
# patch: If user has not defined CC and default value does not exist use gcc
#
ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
#
# Define list of source files, object files, targets, etc
#
# all source files
src :=\
$(wildcard *.c)
# all object files
obj :=\
$(patsubst %.c,%.o,\
$(src))
# all targets
target :=\
$(filter xtest%,\
$(patsubst %.c,%,\
$(src)))
# objects that are required by the targets
lib.o :=\
$(filter-out xtest%,\
$(obj))
# dependency file that will be generated by compiler
deps :=\
$(patsubst %,%.d,\
$(src))
#
# Build rules
#
.PHONY: all
all: $(target) $(obj)
# delete implicit rule for building an executable directly from its source file
% : %.c
# our rule: to build target link its object file against library object files
%: %.o $(lib.o)
$(CC) -o $@ $^ $(LDFLAGS)
# our rule to build objects: also generate a dependency file
%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MP -MF $<.d $<
.PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps) $(obsolete.deps)
#
# Include dependecies (if already generated)
#
-include $(deps)
Cleaning Up After Deleting Source Files
There is still a flaw in our build system. Assume we begin with a clean directory and build everything:
theon$ make gcc -c -MT xtest_main.o -MMD -MP -MF xtest_main.c.d xtest_main.c gcc -c -MT bar.o -MMD -MP -MF bar.c.d bar.c gcc -c -MT foo.o -MMD -MP -MF foo.c.d foo.c gcc -o xtest_main xtest_main.o bar.o foo.o theon$
So far so good. Everything was build. Now we delete dummy.h and dummy.c and rebuild again:
theon$ rm dummy.h dummy.c rm: dummy.h: No such file or directory rm: dummy.c: No such file or directory theon$ make make: Nothing to be done for 'all'. theon$
This also looks good. The target needs to be updated by linking the remaining object files. So all code from dummy.o is now out of the system, forever forgotten. Or is that so? Well, the dummy code is gone but there are some leftovers. Its dependency is still there and make clean will never remove it:
theon$ make clean rm -f xtest_main bar.o foo.o xtest_main.o bar.c.d foo.c.d xtest_main.c.d theon$ ls *.c.d dummy.c.d theon$
A quick solution to that is to great a list of obsolete dependency files. We simply use the wildcard function to get all existing dependency files and filter out what is still needed:
1 2 3 4 | # dependency file leftovers of gone source files
obsolete.deps:=\
$(filter-out $(deps),\
$(wildcard *.c.d))
|
In the clean rule we add this list to the remove command:
1 2 3 | .PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps) $(obsolete.deps)
|
So this completly erases dummy.* from history the next time invoke make clean:
theon$ make gcc -c -MT xtest_main.o -MMD -MP -MF xtest_main.c.d xtest_main.c gcc -c -MT bar.o -MMD -MP -MF bar.c.d bar.c gcc -c -MT dummy.o -MMD -MP -MF dummy.c.d dummy.c gcc -c -MT foo.o -MMD -MP -MF foo.c.d foo.c gcc -o xtest_main xtest_main.o bar.o dummy.o foo.o theon$ rm dummy.h dummy.c theon$ make make: Nothing to be done for 'all'. theon$ make clean rm -f xtest_main bar.o foo.o xtest_main.o bar.c.d foo.c.d xtest_main.c.d dummy.c.d theon$ ls *.c.d || echo "all dependency files are gone" *.c.d: No such file or directory all dependency files are gone theon$
Here the complete makefile
#
# patch: If user has not defined CC and default value does not exist use gcc
#
ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
#
# Define list of source files, object files, targets, etc
#
# all source files
src :=\
$(wildcard *.c)
# all object files
obj :=\
$(patsubst %.c,%.o,\
$(src))
# all targets
target :=\
$(filter xtest%,\
$(patsubst %.c,%,\
$(src)))
# objects that are required by the targets
lib.o :=\
$(filter-out xtest%,\
$(obj))
# dependency file that will be generated by compiler
deps :=\
$(patsubst %,%.d,\
$(src))
# dependency file leftovers of gone source files
obsolete.deps:=\
$(filter-out $(deps),\
$(wildcard *.c.d))
#
# Build rules
#
.PHONY: all
all: $(target) $(obj)
# delete implicit rule for building an executable directly from its source file
% : %.c
# our rule: to build target link its object file against library object files
%: %.o $(lib.o)
$(CC) -o $@ $^ $(LDFLAGS)
# our rule to build objects: also generate a dependency file
%.o: %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MP -MF $<.d $<
.PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps) $(obsolete.deps)
#
# Include dependecies (if already generated)
#
-include $(deps)
But why wait for the next make clean? We could also cleanup the next time we generate an object file or target.
Types of Prerequisites
We can add another phony rule which will remove all obsolete dependency files when it gets triggered:
1 2 3 4 | # rule for removing obsolete dependency files
.PHONY: $(obsolete.deps)
$(obsolete.deps) :
$(RM) $(obsolete.deps)
|
And we want to trigger this rule the next time a target or object file gets created. For that we can change the rules to
1 2 3 4 5 6 7 | # our rule: to build target link its object file against library object files
%: %.o $(lib.o) | $(obsolete.deps)
$(CC) $^ $(LDFLAGS)
# our rule to build objects: also generate a dependency file
%.o: %.c | $(obsolete.deps)
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MP -MF $<.d $<
|
where after the pipe symbol '|' the dependency rule for cleaning up the left overs was added. We added a so called order-only prerequisite (see Types of Prerequisites). It will considered before the normal prerequisites i(the one
on the left hand side of the pipe symbol)
So now we can save the make clean:
theon$ make gcc -c -MT xtest_main.o -MMD -MP -MF xtest_main.c.d xtest_main.c gcc -c -MT bar.o -MMD -MP -MF bar.c.d bar.c gcc -c -MT dummy.o -MMD -MP -MF dummy.c.d dummy.c gcc -c -MT foo.o -MMD -MP -MF foo.c.d foo.c gcc -o xtest_main xtest_main.o bar.o dummy.o foo.o theon$ rm dummy.h dummy.c theon$ make rm -f dummy.c.d theon$ ls *.c.d || echo "all dependency files are gone" bar.c.d foo.c.d xtest_main.c.d theon$
#
# patch: If user has not defined CC and default value does not exist use gcc
#
ifeq ($(origin CC),default)
cc_check := $(shell $(CC) -v > /dev/null 2>&1 && echo "sane")
ifneq ($(strip $(cc_check)),sane)
CC := gcc
endif
endif
#
# Define list of source files, object files, targets, etc
#
# all source files
src :=\
$(wildcard *.c)
# all object files
obj :=\
$(patsubst %.c,%.o,\
$(src))
# all targets
target :=\
$(filter xtest%,\
$(patsubst %.c,%,\
$(src)))
# objects that are required by the targets
lib.o :=\
$(filter-out xtest%,\
$(obj))
# dependency file that will be generated by compiler
deps :=\
$(patsubst %,%.d,\
$(src))
# dependency file leftovers of gone source files
obsolete.deps:=\
$(filter-out $(deps),\
$(wildcard *.c.d))
#
# Build rules
#
.PHONY: all
all: $(target) $(obj)
# rule for removing obsolete dependency files
.PHONY: $(obsolete.deps)
$(obsolete.deps) :
$(RM) $(obsolete.deps)
# delete implicit rule for building an executable directly from its source file
% : %.c
# our rule: to build target link its object file against library object files
%: %.o $(lib.o) | $(obsolete.deps)
$(CC) -o $@ $^ $(LDFLAGS)
# our rule to build objects: also generate a dependency file
%.o: %.c | $(obsolete.deps)
$(CC) -c $(CPPFLAGS) $(CFLAGS) -MT $@ -MMD -MP -MF $<.d $<
.PHONY: clean
clean:
$(RM) $(target) $(obj) $(deps) $(obsolete.deps)
#
# Include dependencies (if already generated)
#
-include $(deps)
Is That All?
Certainly not. Currently we still have the problem that each target gets linked against all object files. But in general a target just needs to be linked against a few of these object files. We will later solve this problem by using static libraries where the linker can pick what is actually needed.
-