views:

903

answers:

3

I have a C++ small project using GNU Make. I'd like to be able to turn the following source files:

src/
  a.cpp
  b/
    b.cpp
  c/
    c.cpp

into the following output structure (I'm not concerned about duplicates at this point):

build/
  a.o
  b.o
  c.o

So far I have the following, which unfortunately puts the .o and .d right next to each .cpp:

OBJS            :=      $(foreach file,$(SRCS),$(file).o)
DEPS            :=      $(patsubst %.o,%.d,$(OBJS))
sinclude $(DEPS)

$(OBJS) : %.o : %.cpp
        @echo Compiling $<
        $(CC) $(CC_FLAGS) $(INCS) -MMD -o $@ $<

I'm aware of the $(notdir ...) function, but at this point my efforts to use it to filter the objects has failed. Can anyone shed some light on this? It seems like fairly reasonable thing to do.

+2  A: 
vpath %.cpp src src/b src/c

Then refer to source files without their directory name; Make will search the vpath.

wrang-wrang
+2  A: 

There are at least two ways you can do this. First (and what I'd recommend) is you can add the build directory to the target names (even when using a pattern rule). For example:

$(OBJS) : build/%.o : %.cpp

Second, you can use the VPATH variable to tell make to search a different directory for prerequisites. This is probably the more commonly (over) used approach. It has at least one serious drawback, and that is if you go with it, and later run into problems with "duplicates", there's no way to solve the problem. With the former approach, you can always mirror the source directory structure underneath the build directory to avoid duplicates clashing.

Edit: My previous answer was a little short on detail, so I will expand upon it to show that this actually works as advertised. Here is a complete working example Makefile that uses the first technique described above to solve the problem. Simply paste this into a Makefile and run make -- it will do the rest and show that this does in fact work.

Edit: I can't figure out how to get SO to allow tab characters in my answer text (it replaced them with spaces). After copying and pasting this example, you'll need to convert the leading spaces in the command scripts into tabs.

BUILD_DIR := build

SRCS := \
    a.c \
    b.c \
    c.c \
    a/a.c \
    b/b.c \
    c/c.c

OBJS := $(patsubst %,${BUILD_DIR}/%,${SRCS:%.c=%.o})

foo: ${OBJS}
    @echo Linking $@ using $?
    @touch foo

${BUILD_DIR}/%.o: %.c
    @mkdir -p $(dir $@)
    @echo Compiling $< ...
    @touch $@

${SRCS}:
    @echo Creating $@
    @mkdir -p $(dir $@)
    @touch $@

.PHONY: clean
clean:
    rm -f foo
    rm -f ${OBJS}

In particular, note that there are source files with duplicate names (a.c and a/a.c, b.c and b/b.c, etc) and that this doesn't cause any problems. Also note there is no use of VPATH, which I recommend to avoid using due to its inherent limitations.

Dan Moulding
Ok, using this approach I'd probably need an extra rule to create sub-folders within the output folder?
Justicle
I ended up going down this route - as I needed to make target and configuration specific build folders anyway, it was easy to add another couple of folders to the "buildfolders" step. Thanks!
Justicle
A separate rule for the build folders should work just fine. What I typically do, though, is just add a "@mkdir -p $(dir $@)" line to the command script to create the output directory (if it doesn't already exist) as part of making the object file(s).
Dan Moulding
The first solution will either fail without vpath or fail when you try to link the objects into an executable, depending on where you run it. The "duplicates" objection applies to the "build/" solution just as much as to the the vpath solution.
Beta
@Beta: This is not some hypothetical solution. This is a real-world solution that has been tried, tested, and fielded in numerous Makefiles that I've personally written. It does not require VPATH and it does not suffer problems when there are duplicate file names. Note that the entire solution isn't shown in this answer -- just enough to answer the question.
Dan Moulding
@Dan Moulding: please show your entire solution (or email to [email protected]), and if it works as you say I'll eat my words and reverse my vote.
Beta
@Beta: Done. I suppose my first answer left a little much for the reader to imagine. This example should illustrate the whole thing.
Dan Moulding
This does not satisfy the output structure in the original question. I admit I didn't notice Justicle's comments accepting this change, but it's not fair to modify the problem and criticize another approach that doesn't.
Beta
@Beta: Fair enough. I included VPATH in my answer, as it can do exactly the output structure that he specified. However, it seemed clear to me that his real desire was to *not* get the generated files intermixed with the sources. So, I proposed an alternative solution that doesn't have the limitations of VPATH. Think of it as going the extra mile ;)
Dan Moulding
Fair enough. P.S.: OBJS := ${SRCS:%.c=${BUILD_DIR}/%.o}
Beta
Ah. Nice. I've never thought to use more than the "generic" pattern in a substitution reference. I like it.
Dan Moulding
+1  A: 

Creating the flat directory structure as initially requested.

Does use vpath to track down the source files, but all the bookeeping is done automagically from the SRC list.

SRC = a/a.c a/aa.c b/b.c
TARGET = done

FILES = $(notdir $(SRC) )
#make list of source paths, sort also removes duplicates
PATHS = $(sort $(dir $(SRC) ) )

BUILD_DIR = build
OBJ = $(addprefix $(BUILD_DIR)/, $(FILES:.c=.o))
DEP = $(OBJ:.o=.d)

# default target before includes
all: $(TARGET)

include $(DEP)

vpath %.c $(PATHS)

# create dummy dependency files to bootstrap the process
%.d:
    echo a=1 >$@

$(BUILD_DIR)/%.o:%.c
    echo $@: $< > $(patsubst %.o,%.d,$@)
    echo $< >$@

$(TARGET): $(OBJ)
    echo $^ > $@

.PHONY: clean
clean:
    del $(BUILD_DIR)/*.o
    del $(BUILD_DIR)/*.d

Sorry about the ugly echo's but I only had my win box to test it on.

LarryH