Writing Makefiles

 

 

    (August 2006)

Autoconf and automake are not always the answer.

For many projects, what you need is a simple Makefile that takes care of source code dependencies over a directory tree. My usual programming needs are more than adequately covered with this:

# The directories containing the source files, separated by ':' VPATH=src:../Library # To make "debug" the default configuration if invoked with just "make": # # ifeq ($(CFG),) # CFG=debug # endif # The source files: regardless of where they reside in the source tree, # VPATH will locate them... Group0_SRC = \ Source1.cpp \ Source2.cpp \ LibrarySource1.cpp \ LibrarySource2.cpp # Build a Dependency list and an Object list, by replacing the .cpp # extension to .d for dependency files, and .o for object files. Group0_DEP = $(patsubst %.cpp, deps.$(CFG)/Group0_%.d, ${Group0_SRC}) Group0_OBJ = $(patsubst %.cpp, objs.$(CFG)/Group0_%.o, ${Group0_SRC}) # Your final binary TARGET=applicationName # What compiler to use for generating dependencies: # it will be invoked with -MM -MP CXXDEP = g++ # What include flags to pass to the compiler INCLUDEFLAGS= -I ../Library -I src # Separate compile options per configuration ifeq ($(CFG),debug) CXXFLAGS += -g -Wall -D_DEBUG ${INCLUDEFLAGS} else CXXFLAGS += -O2 -Wall ${INCLUDEFLAGS} endif # A common link flag for all configurations LDFLAGS += -lSDL all: inform bin.$(CFG)/${TARGET} inform: ifneq ($(CFG),release) ifneq ($(CFG),debug) @echo "Invalid configuration "$(CFG)" specified." @echo "You must specify a configuration when running make, e.g." @echo "make CFG=debug" @echo @echo "Possible choices for configuration are 'release' and 'debug'" @exit 1 endif endif @echo "Configuration "$(CFG) @echo "------------------------" bin.$(CFG)/${TARGET}: ${Group0_OBJ} | inform @mkdir -p $(dir $@) $(CXX) -g -o $@ $^ ${LDFLAGS} objs.$(CFG)/Group0_%.o: %.cpp @mkdir -p $(dir $@) $(CXX) -c $(CXXFLAGS) -o $@ $< deps.$(CFG)/Group0_%.d: %.cpp @mkdir -p $(dir $@) @echo Generating dependencies for $< @set -e ; $(CXXDEP) -MM -MP $(INCLUDEFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,objs.$(CFG)\/Group0_\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$ clean: @rm -rf \ deps.debug objs.debug bin.debug \ deps.release objs.release bin.release # Unless "make clean" is called, include the dependency files # which are auto-generated. Don't fail if they are missing # (-include), since they will be missing in the first invocation! ifneq ($(MAKECMDGOALS),clean) -include ${Group0_DEP} endif
Some comments:
  • VPATH
    With VPATH, you can instruct make to search for your source files in alternate directories. This way, you can automatically handle a complicated tree of sources - let make worry about finding your source. You only specify source filenames, not their paths (in Group0_SRC). Which means that you must NOT use the same filename in two different VPATH directories - only the first one will be used, if you do.
  • Debug and Release configurations
    Just as in other developing platforms, you can define different compilation options for debug / release builds (e.g. the CXXFLAGS in the template above) and build with...
    make CFG=debug
    or
    make CFG=release
    Your output object files will be placed in objs.debug or objs.release, and your application under bin.debug or bin.release, respectively.
  • Dependencies
    These will be created automatically from your C++ source code, and updated automatically if you change your code. The way this works is by instructing make to read an external makefile, via the line
    -include ${Group0_DEP}
    
    Group0_DEP has been calculated (see the top of the template) to mirror your set of source files. If you are compiling in CFG=debug mode, for example...
     
    Group0_SRC entriesGroup0_DEP entries
    Source1.cppdeps.debug/Group0_Source1.d
    Source2.cppdeps.debug/Group0_Source2.d
    LibrarySource1.cppdeps.debug/Group0_LibrarySource1.d
    LibrarySource2.cppdeps.debug/Group0_LibrarySource2.d
    Names of external dependency files

    So make is instructed to include these external makefiles (these .d files). When first invoked, it notices that these files do not exist, but also realizes that it has a pattern rule (the deps.$(CFG)/Group0_%.d: %.cpp line above) that instructs on how to create them. It therefore executes these rules, which invoke gcc with -MM and -MP and automatically create the dependency rules in the .d files. When these rules are finished, make realizes that external makefiles have changed, and re-includes them - thus learning of all source files dependencies.

  • Clean rule
    This rule will clean dependencies, object files and binaries if you invoke make clean.
  • Multiple CPUs/cores
    If the " | inform" was missing from the bin.$(CFG)/${TARGET}: ... rule, and you tried to invoke make for multiple CPUs/cores with something like...
    make -j2
    ...you'd find that the first CPU would try the inform rule (displaying the message about CFG missing) while the other would try to implement bin.$(CFG)/${TARGET}, which, with CFG missing, would fail! The " | inform" tells make that this rule has an order dependency on rule inform.
Even though what you see here is for C++ code, the principles demonstrated can be very easily adapted to other languages (dependency output, accessing source files via VPATH, etc).

What to do if you have auto-generated code

The previous template will cover the needs of the vast majority of coders. There are however those few, those happy few, that after having written hundreds of thousands of lines, realize that it would be much better if machines wrote some of them. And they learn about flex and bison and about ANLTR; and they start writing Perl and Python scripts that actually write code for them...

If the above sounds like gibberish, you'd better stop here and go read the Pragmatic Programmer.

If they don't, however, you may not know why code generators pose a problem when using Makefiles. If we use...

  • a magic Perl script named 'roboCoder.pl' that reads...
  • a file named 'documentation.tex'...
  • and creates two source files, 'robotMade.h' and 'robotMade.c'
...then, unfortunately, this Makefile section does not do what you would expect it to do:
all:	codeGen applicationName

codeGen:	robotMade.c robotMade.h

robotMade.c robotMade.h:	documentation.tex
	./roboCoder.pl documentation.tex

applicationName: $(OBJS) | codeGen
...
You see, make interprets this last rule as two rules:
robotMade.c:	documentation.tex
	./roboCoder.pl documentation.tex

robotMade.h:	documentation.tex
	./roboCoder.pl documentation.tex
...which is probably not what you want. 'roboCoder.pl' will run twice, whenever you change documentation.tex. This might sound like a minor inconvenience, but if you use the "-j" options, you might jolly well have two make instances running one roboCoder each... All hell might break loose (racing conditions when roboCoder opens its output files, etc).

There's no point in repeating - please go and read the perfect description of the problem in automake's documentation (in the automake's FAQ section, read the "Multiple Outputs" entry).

If you adopt the solutions described there and try to put them to use in the template I offered above, you'll face a problem: calculation of the external dependencies (the .d files described in the previous section) needs access to the .h/.c/.cpp files generated, so the code generation must somehow run before the dependencies generation. The dependencies generation, however, is based on the inclusion of external makefiles; if you just add the 'codeGen' rule as a prerequisite of your 'applicationName', it won't do; included makefiles are handled by make before any other rules. The only workaround I can offer is to add your 'codeGen' rule, as an order dependency, on the .d files themselves:

deps.$(CFG)/Group0_%.d: %.cpp | codeGen
	@mkdir -p $(dir $@)
	@echo Generating dependencies for $<
	...
This will instruct make to invoke your code generation rule first, and then proceed to calculate the external dependencies (that is, after the .h/.c/.cpp files have been created).
Back to homepageLast update on: Wed Oct 22 13:47:23 2008 (Valid HTMLValid CSS)