3

Let's say I have two rules for making object files (each in obj/), one that depends on the .cpp files in directory A - let's call it src - and one that depends on the .cpp files directory B, which is a subdirectory of A - call it src/demos. Their rules look like this:

$(ODIR)/%.o : $(SRC)/%.cpp $(DEPS)
    $(CXX) -c -o $@ $< $(CXXFLAGS)

$(ODIR)/%.o : $(DEMODIR)/%.cpp $(DEPS)
    $(CXX) -c -o $@ $< $(CXXFLAGS)

As you can see, I am repeating a recipe in making two rules for these directories. Is there a way I can "compress" these two rules into one? I thought about using a wildcard in some way to match to both src/*.cpp and src/demos/*.cpp, but I am not sure how that would be implemented in a rule.

Thanks in advance!

Catyre
  • 77
  • 7
  • Does this answer your question? [Makefiles with source files in different directories](https://stackoverflow.com/questions/1139271/makefiles-with-source-files-in-different-directories) – Greenonline Dec 10 '22 at 22:18
  • 1
    The accepted answer there is not the best. Definitely the `VPATH` method is better. – MadScientist Dec 10 '22 at 22:21
  • Actually, I think [this answer](https://stackoverflow.com/a/54790486/4424636) to [Single Makefile for sources in multiple sub directories](https://stackoverflow.com/q/54790033/4424636) might be an option... – Greenonline Dec 10 '22 at 22:21

1 Answers1

1

There are two solutions to this.

  1. The simple one that places object files in a different directory and is robust against name clashes.
  2. The one that preserves your exact directory structure but is a bit more complicated and not robust against name clashes.

Preamble variables used in both solutions (they will partly look differently in your real setting)

SRC=src/
DEMODIR=src/demos/
ODIR=out/
DEPS=

sources=$(wildcard $(SRC)*.cpp) $(wildcard $(DEMODIR)*.cpp)

Solution 1 (my recommendation).

Object files follow the directory structure of source files.

objects=$(patsubst %.cpp,$(ODIR)%.o,$(sources))
$(objects): $(ODIR)%.o: %.cpp $(DEPS)
    $(CXX) -c -o $@ $< $(CXXFLAGS)

Solution 2 (your directory structure).

objects=$(patsubst %.cpp,$(ODIR)%.o,$(sources))
flatobjects=$(patsubst $(ODIR)$(SRC)%, $(ODIR)%, $(objects))
f_retrieve=$$(findstring $(join $(SRC),$(1:.o=.cpp)),$(sources)) $$(findstring $(join $(DEMODIR),$(1:.o=.cpp)),$(sources))

.SECONDEXPANSION:
$(flatobjects): $(ODIR)%.o: $(call f_retrieve,%.o) $(DEPS)
    $(CXX) -c -o $@ $< $(CXXFLAGS)

This will likely break the second you have both src/a.cpp and src/demos/a.cpp, so Solution 1 is recommended on that basis alone.

The trick is to search for each target's prequisite in the $(sources) variable for a matching source file (via findstring). Note the .SECONDEXPANSION section and the double $ before findstring. Doing this directly should work in theory, but GNU make doesn't like it inside the rule. Relaxing the expression to be expanded twice then works.

Demo (solution 2)

./src: a.cpp b.cpp c.cpp
./src/demos: x.cpp y.cpp z.cpp

Additional helper rule: flatobjects: $(flatobjects)

$ make flatobjects 
gcc -c -o out/a.o src/a.cpp 
gcc -c -o out/b.o src/b.cpp 
gcc -c -o out/c.o src/c.cpp 
gcc -c -o out/x.o src/demos/x.cpp 
gcc -c -o out/y.o src/demos/y.cpp 
gcc -c -o out/z.o src/demos/z.cpp 
bitmask
  • 32,434
  • 14
  • 99
  • 159
  • 1
    Thanks for the help, I ended up going with your first suggestion. I had thought earlier to make my `obj` folder mirror my `src` folder, but didn't follow through with the idea because I figured I wasn't thinking about it correctly. – Catyre Dec 11 '22 at 19:00