Zack
Zack

Reputation: 6606

Makefile single recipe for multiple patterns

Is there a simple way to apply a single recipe to multiple pattern rules?

Consider this directory structure

|- src
|  - file1.cpp
|- test
|  - file2.cpp
|  - file3.cpp
|  - metrics
|    - file4.cpp
|    - file5.cpp

I would like to write a single pattern rule to compile all the .cpp files in the test directory. Here is what I have right now:

$(OBJS)/%.o: test/%.cpp
    @mkdir -p $(OBJS)
    g++ $(FLAGS) $(CPPFLAGS) -c $< -o $@

$(OBJS)/%.o: test/metrics/%.cpp
    @mkdir -p $(OBJS)
    g++ $(FLAGS) $(CPPFLAGS) -I test -c $< -o $@

TEST_CPP := $(wildcard test/*.cpp) $(wildcard test/**/*.cpp)
TEST_OBJ :=  $(addprefix $(OBJS)/,$(notdir $(TEST_CPP:.cpp=.o)))

$(BIN)/testRunner: $(TEST_OBJ)
    @mkdir -p $(BIN)
    g++ $(FLAGS) $(CPPFLAGS) $^ $(LIBS) -o $@

I would like to avoid repeating the recipe for the object files. I imagine the solution would look something like this:

$(OBJS)/%.o: test/%.cpp
$(OBJS)/%.o: test/metrics/%.cpp 
    @mkdir -p $(OBJS)
    g++ $(FLAGS) $(CPPFLAGS) -c $< -o $@

(At the moment, the objs directory is flat; but, I don't have a problem duplicating the source directory structure if that simplifies the makefile.)

Upvotes: 4

Views: 2176

Answers (1)

MadScientist
MadScientist

Reputation: 100836

There is no way to do what you want. The closest you can get is to put the recipe into a variable and use that same variable in each recipe, like this:

define BUILD_O
        @mkdir -p $(OBJS)
        g++ $(FLAGS) $(CPPFLAGS) -c $< -o $@
endef

$(OBJS)/%.o : test/%.cpp
        $(BUILD_O)
$(OBJS)/%.o : test/metrics/%.cpp
        $(BUILD_O)

As an alternative to that, because you want all your objects to go into the same directory but find sources from different directories, you could use VPATH instead and write just a single pattern rule, like this:

VPATH = test test/metrics

$(OBJS)/%.o : %.cpp
        @mkdir -p $(@D)
        g++ $(FLAGS) $(CPPFLAGS) -c $< -o $@

I strongly urge you to rework your makefiles to use the standard variable names, though; use $(CXX), not g++, for the C++ compiler name and $(CXXFLAGS), not $(FLAGS), for the C++ compiler options.

EDIT

If you need to customize flags, then the normal way to do this is either through target-specific variables, or through constructed macro names

Target-specific variables would look like this:

test: $(test_targets)
test: FLAGS += -Dtest

production: $(production_targets)
production: FLAGS += -Dproduction

then when you run make test you'll get the -Dtest added; when you run make production you'll get the -Dproduction added. There are issues here though: if you run make myfoo.o then you won't get these added (see the documentation).

Constructed macro names would look like this:

test_FLAGS = -Dtest
production_FLAGS = -Dproduction

rootdir = $(firstword $(subst /, ,$1))

VPATH = test test/metrics production production/widgets

$(OBJS)/%.o : %.cpp
        @mkdir -p $(@D)
        g++ $(FLAGS) $(CPPFLAGS) $($(call rootdir,$(<D))_FLAGS) -c $< -o $@

Upvotes: 4

Related Questions