submit to programming reddit
 
(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: 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...

...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).

profile for ttsiodras at Stack Overflow, Q&A for professional and enthusiast programmers
GitHub member ttsiodras
My CV  About me  Talk to me  Back to indexLast update on: Fri Aug 9 22:48:30 2013 (Valid HTML)

comments powered by Disqus