CC Artwork - Stuck In Customs http://www.stuckincustoms.com/ by Trey Ratcliff

ZenSRC

One geek's ravings on software, development, games, and technology.

Archive for the ‘makefile’ tag

Byzantine Generals Coding Sample, Part 2 (Makefiles)

June 6th, 2010

This is a follow up article to my C++ Byzantine Generals Coding Sample which focuses on the Makefile part of the project.

Over the years, I’ve heard many complaints directed at Make and Makefiles (and I’ve done a bit of it myself). I think the biggest problem coders have with Makefiles is that they are nearly a black art in and of themselves. I think most programmers would rather spend time on the actual code they write than on figuring out the sometimes complex syntax that gets their compiler to build their source files in an efficient manor.

This post is intended to pull together some of the best practices I’ve found and to discuss some of the neat tasks that I have my Makefile doing in the hopes that it helps get you to a solution faster.

First up, the help target. You’ll notice in the full Makefile that the help target appears first. Many makefiles typically have “all” first, which usually makes the default behavior perform a complete build. I’ve found that printing usage information on the available targets far more preferable. It gives new users an idea of what the makefile does, and what to expect from each target.

.PHONY: help
help:
	@echo 'Commonly used make targets:'
	@echo '  all          - build program (use release and debug targets instead)'
	@echo '  check        - build program and run valgrind memory check tests'
	@echo '  clean        - remove files created by other targets'
	@echo '  debug        - build program with debugging enabled and optimizations disabled'
	@echo '  docs         - run doxygen to produce documentation'
	@echo '  graph        - build program and run, generating output graphs'
	@echo '  print        - creates a pdf listing of the source code'
	@echo '  release      - build program with debugging disabled and optimizations enabled'
	@echo '  TAGS         - build TAGS database for source code'

Next, we provide targets for release and debug. All we’re doing here is modifying the $(CFLAGS) variable such that it uses the correct compilation flags for the relevant target. These get modified and passed on to the “all” target and that way we keep the rest of the rules clean.

#
# We use target-specific variable values for debug and release build targets,
# see <http://www.gnu.org/software/make/manual/make.html#Target_002dspecific>
#
.PHONY: debug
debug: CFLAGS += $(CDEBUG)
debug: all

.PHONY: release
release: CFLAGS += $(CRELEASE)
release: all

Next up is the heart of the compilation part of the makefile. This line literally says that for each of the object files we need to build, use the corresponding C++ (.cpp) file from the source directory. Prior to constructing and passing the compilation line to the shell, we perform static code analysis on our input source files. Essentially, we’re stacking the deck in our favor by attempting to catch subtle errors early and often. In the event that the static analysis tool (cppcheck) finds anything, it will force the compilation to halt and require the developer to address the relevant issue. Finally, I found a great resource concerning correctly generating the list of dependencies, and thats where the dependency generation lines originate.

# Simple brute force rule to build obj/*.o based on their src/*.cpp counterparts
# Dependency generation reference <http://make.paulandlesley.org/autodep.html>
#
# Also perform static analysis on all of the files, in another effort to
# stack the deck in our favor and catch errors early (Force our static
# analysis tool to return an error and halt the makefile when it finds
# problems).
.PHONY: $(objdir)%.o
$(objdir)%.o: $(srcdir)%.cpp
	@mkdir -p $(dir $@)
	@echo "============="
	@echo -n "Static analysis: "
	cppcheck -q --error-exitcode=1 $<
	@echo "Compiling $<"
	$(CC) $(CFLAGS) -c $< -o $@
	@echo "Dependency generation $<"
	@cp $(objdir)$*.d $(objdir)$*.P; \
		sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
		-e '/^$$/ d' -e 's/$$/ :/' < $(objdir)$*.d >> $(objdir)$*.P; \
		rm -f $(objdir)$*.d

This next target, “check” is fairly straight forward, but immensely useful. It runs the excellent Valgrind memory analysis and profiling tool over our project. Ideally, this would be incorporated into a batch of automated unit tests, but more on that later. It’s enough to say that we’re taking another step towards detecting issues early and often by providing a common and simple way for all developers to run this tool via the make system.

.PHONY: check
check: debug
	@echo "============="
	@echo "Performing valgrind analysis"
	valgrind --track-origins=yes --leak-check=full $(TARGET) $(TARGET_BASE_ARGUMENTS)

Another simple target is the automated generation of Doxygen documentation. I have the Doxyfile set to spit out warnings to a text file, doxy_warn.txt, so the developer can visit it after the make process and take care of any issues that were found. Doxygen is an immensely useful tool, and when leveraged correctly, can make the process of providing API documentation to third parties especially nice. Here is the doxygen documentation that my coding sample generated.

.PHONY: docs
docs: graph
	@echo "============="
	@echo "Creating Documentation using doxygen"
	doxygen ../doc/Doxyfile
	cat ../doc/doxy_warn.txt

Finally, the “TAGS” target. Exuberant Ctags is another terrific development tool that allows developers to quickly navigate source code. I have the TAGS target rebuilt on “all”. This keeps our source code database fresh.

# Do not add phony rule, we want TAGS to be checked to save a little build time.
TAGS:
	@echo "============="
	@echo "Creating TAGS"
	etags -o $(srcdir)TAGS $(srcdir)*.cpp  $(srcdir)*.h

So here are a few things that this makefile doesn’t do, which it normally could. These aren’t done mostly because the coding sample I wrote didn’t require it.

  • Automate unit testing (run unit tests, verify passage, generate and distribute reports).
  • The makefile isn’t setup to do builds which require multiple components built across multiple directories.
  • Doesn’t perform installation.
  • Doesn’t dependency checks.

Posted in Development, Technology

Tagged with ,