If you’re more or less successful in your debugging session, it’s quite likely that you’ll have to modify some source code so you can actually fix a bug. And if you’re more or less careful, you might want to validate your changes actually work. We saw some time ago that you don’t need to restart gdb after a recompile because gdb is already smart enough to know that the binary changed.
Turns out you don’t even need to drop from gdb to a shell: just type make (using the parameters you’d usually call make with) and watch gdb take care of building your binary again.
Rebuilding your project like this is not only useful to save time: you can also keep your breakpoints and they should still make sense, assuming you didn’t refactor your code too much.
I always forget about two very useful make variables, so I’ll leave this here: $(COMPILE.cpp), $(LINK.cpp).
It’s easy, instead of writing a rule as
foo.o: foo.cpp g++ -c foo.coo
you should instead write this:
foo.o: foo.cpp $(COMPILE.cpp) foo.coo
COMPILE.cpp will have the default compiler you are supposed to use, and probably some helpful parameters as well. Likewise, LINK.cpp will have the linker you are supposed to use.
There are many useful predefined variables in make. Be sure to check them all by running “make -p” in a console.
|So, after learning how to use makefiles, then how to use makefiles for TDD and for code coverage report, now you need to annoy your whole team with a talking makefile. What could be better to notify everyone on your team when a test fails than a synthesized voice commanding you to fix your program?|
test: $(TEST_SRCS) @for TEST in $(TEST_BINS); do make "$$TEST"; echo "Execute $(TEST)"; if ! ./$$TEST; then echo "Oh noes! I detected a failed test from $$TEST. Go and fix your program!" | festival --tts ; done
Try it. You’ll love it.
Bonus chatter: when Valgrind detects over $MUCHOS errors it’ll print “Too many errors detected. Go and fix your program”, then it won’t print so much detail in the next backtraces.
This time we’ll depend on two tools, gcov and gtest. These are in Ubuntu’s repositories, so you should have no problem getting them. I won’t even bother to explain this makefile (not because it’s obvious but because I don’t really remember how it works. I wrote this over a year ago).
.PHONY: clean coverage_report coverage_report: # Reset code coverage counters and clean up previous reports rm -rf coverage_report lcov --zerocounters --directory . $(MAKE) COMPILE_TYPE=code_coverage && $(MAKE) COMPILE_TYPE=code_coverage test lcov --capture --directory $(BIN_DIR)/$(OBJ_DIR)/code_coverage --base-directory . -o salida.out && lcov --remove salida.out "*usr/include*" -o salida.out && genhtml -o coverage_report salida.out rm salida.out
Bonus makefile target: make your code pretty:
.PHONY: pretty pretty: find -L|egrep '.(cpp|h|hh)$$'|egrep -v 'svn|_Test.cpp$$' | xargs astyle --options=none
Remember to change your astyle options as needed.
Bonus II: Example project using gcov and gtest: gcov_gtest_sample.tar. The irony? It doesn’t use my common makefile, it predates it.
So, after reading my post about makefiles you decided that you like them but would like to add some TDD to be buzzword compliant? No problem, that’s easy to do.
Assuming you use a naming convention such as this one:
path/to/src/Object.h path/to/src/Object.cpp path/to/src/Object_Test.cpp
then it’s easy to auto detect which tests should be built:
TEST_SRCS := $(patsubst ./%, %, $(shell find -L|grep -v svn|egrep "_Test.cpp$$" ) ) TEST_BINS := $(addprefix ./$(BIN_DIR)/, $(patsubst %.cpp, %, $(TEST_SRCS)) )
Then we have to define a special rule with pattern matching to compile the tests:
$(BIN_DIR)/%_Test: $(patsubst $(BIN_DIR)/%, %, %_Test.cpp ) %.cpp %.h @echo "Making $@" @mkdir -p $(shell dirname $@) g++ $(CXXFLAGS) -g3 -O0 $< -o $@ -lpthread -lgtest_main -lgmock $(OBJECTS) $(LDFLAGS)
and some magic to auto execute every test when we “make test”:
test: $(TEST_SRCS) @for TEST in $(TEST_BINS); do make "$$TEST"; echo "Execute $(TEST)"; ./$$TEST; done
Everything nice and tidy for a copy & paste session:
TEST_SRCS := $(patsubst ./%, %, $(shell find -L|grep -v svn|egrep "_Test.cpp$$" ) ) TEST_BINS := $(addprefix ./$(BIN_DIR)/, $(patsubst %.cpp, %, $(TEST_SRCS)) ) $(BIN_DIR)/%_Test: $(patsubst $(BIN_DIR)/%, %, %_Test.cpp ) %.cpp %.h @echo "Making $@" @mkdir -p $(shell dirname $@) g++ $(CXXFLAGS) -g3 -O0 $< -o $@ -lpthread -lgtest_main -lgmock $(OBJECTS) $(LDFLAGS) .PHONY: test test: $(TEST_SRCS) @for TEST in $(TEST_BINS); do make "$$TEST"; echo "Execute $(TEST)"; ./$$TEST; done
Now you just need to run make test. Remember to add the proper Vim’s mapping.
For open source projects, makefiles are a must. All C++ projects need them, even though cmake is strong nowadays, and even though Java has its own version (actually, several of them, but that’s not important now) a makefile could be used.
Even if it is an ubiquitous build system, it is pretty much outdated nowadays, and although using its basic features is easy, mastering it is a complex task. Worst still, mastering makefiles means you’ll probably produce write-only-code, and as makefiles are code themselves, and must therefore be maintained, this can be a nuisance to a newcomer to your project.
There’s an upside to makefiles being code: they can be reused. Once you find a configuration that suits your development process, you don’t need to write it again. I’ll post here some of the main targets I ussually include in a common.mk. As I mentioned, it’s mostly write-only-code, yet you may find it useful:
# Dependency directoy df=$(BUILD_DIR)/$(*D)/$(*F) $(OBJECTS): $(BUILD_DIR)/%.o: %.cpp @mkdir -p $(BUILD_DIR)/$(*D) $(COMPILE.cpp) -MD -o $@ $< @cp $(df).d $(df).P; sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\$$//' -e '/^$$/ d' -e 's/$$/ :/' < $(df).d >> $(df).P; rm -f $(df).d $(MAIN_OBJ): $(MAIN_SRC) $(COMPILE.cpp) -MD -o $@ $< # Binary name depends on BIN_DIR/BIN_NAME, so the call to create BIN can # be forwarded to BIN_DIR/BIN_NAME $(BINARY): $(BIN_DIR)/$(BINARY) $(BIN_DIR)/$(BINARY): $(OBJECTS) $(DEPS_OBJECTS) $(MAIN_OBJ) @mkdir -p $(BIN_DIR) @# Workaround for a linker bug: if the libs are not @# at the end it won't link (something to do with how the linker @# lists the dependencies... too long for a comment, rtfm g++ $(CXXFLAGS) $^ -o $(BIN_DIR)/$@ $(LDFLAGS) @#$(LINK.cpp) $^ -o $@ -include $(DEPENDS)
How is this used? Well, don’t even try to understand the dependency autogeneration, it’ll make your head explode.
$(OBJECTS): $(BUILD_DIR)/%.o: %.cpp
This defines a rule for building .o objects; a variable named OBJECTS should be present when including this file.
A special rule is defined for a main object (actually this is needed to compile the tests, which we’ll do next time, since you may have a different main function).
$(BINARY): $(BIN_DIR)/$(BINARY) $(BIN_DIR)/$(BINARY): $(OBJECTS) $(DEPS_OBJECTS) $(MAIN_OBJ)
And finally, a rule for to create the real binary. Next time I’ll add some cool features for TDD to this makefile.