Friday, September 16, 2016

Google Test, generated source, and GNU Make

I had trouble with this arrangement:

  • Using Pro*C for a client to generate "C" files from .pc sources
  • Google Test for unit testing
  • GNU Make

What was the problem?

Ideally I could write a pattern rule like this:

%-test.o: %.c %-test.cc

This means when I want to compile my C++ test source, it required make first run the Pro*C preprocessor to generate a "C" source used in the test. Why? Google tests follow this template:

#include "source-to-test.c"
#include <gtest/gtest.h>
// Tests follow

Google test includes your source file (not header) so the test code has access to static variables and functions (think "private" if you're from Java or C#).

So my problem is make is very clever with rules like:

%.foo: %.bar
%.qux: %.foo

And knows that if you want a "bob.qux", which needs a "bob.foo", and there's no "bob.foo" but there is a file named "foo.bar", make follows the recipe for turning a "bar" into a "foo", and this satisfies the rule for "bob.qux".

However the simple rule I guessed at:

%-test.o: %.c %-test.cc

Doesn't work! GNU Make has a corner case when there are multiple prerequisites (dependencies), and won't make missing files even where there's another rule saying how to do so.

There is another way to state what I want:

%-test.o: %-test.cc: %.c

This is called a static rule. It looks promising, but again doesn't work. GNU make does not support patterns (the "%") in static rules. I would need to write each case out explicitly, e.g.:

a-test.o: a-test.cc: a.c

While this does work, it's also a problem.


What's wrong with being explicit?

Nothing is wrong with "explicit" per se. Usually it's a good thing. In this case, it clashes with the rule of "say it once". For each test module a programmer writes, he would need to edit the Makefile with a new, duplicative rule. So when new tests break, instead of thinking "my code is wrong", he needs to ask "is it my code, or my build?" Extra cognitive burden.


What does work?

There is a way to get the benefit of a static rule without the duplication, but it's hackery—good hackery, to be sure, but violating the "rule of least surprise". Use make's powers to rewrite the Makefile at run time:

define BUILD_test
$(1:%=%.o): $(1:%-test=%.c) $(1:%=%.cc)
        $$(COMPILE.cc) $$(OUTPUT_OPTION) $(1:%=%.cc)
$(1): $(1:%=%.o)
endef
$(foreach t,$(wildcard *-test.cc),$(eval $(call BUILD_test,$(t:%.cc=%))))

What a mouthful! If I have a "foo-test.cc" file, make inserts these rules into the build:

foo-test.o: foo.c foo-test.cc
        $(COMPILE.cc) $(OUTPUT_OPTION) foo-test.cc
foo-test: foo-test.o

I'd like something simpler, less inscrutable. Suggestions welcome!

No comments: