makefilegnu-makevpath

Make: Utilizing VPATH to compile and link files with a different extension


I have a somewhat odd Make case that I'm struggling to figure out. I have a C++ project I'm starting with some .cpp files, and I need to compile some source files from multiple remote directories that are utilized by several applications. This is not ideal, but this is the legacy directory/compilation structure my dev group utilizes.

Other projects utilize VPATH to accomplish this. However, they end up hardcoding the list of required objects that get linked, and this approach does not seem elegant or flexible to me. I want to dynamically compile and link everything in. Another potential complication is that some of these remote VPATH directories have files with .c extensions (even though these compile as C++) rather than .cpp.

My current solution:

# Directory for object files 
OBJ_DIR = build

CPPSRC := $(wildcard *.cpp)
CSRC   := $(wildcard *.c)
OBJ    := $(CSRC:%.c=$(OBJ_DIR)/%.o) $(CPPSRC:%.cpp=$(OBJ_DIR)/%.o)

# Directory for output binaries
BIN_DIR = bin

# Name of binary executable
OUTPUT = foo

# VPATH allows you to specify other directories to search for prereqs in
VPATH = /one/remote/dir /another/remote/dir

# 'make all'
all: $(BIN_DIR)/$(OUTPUT)

# 'make clean'
clean: 
    @rm -rf $(BIN_DIR)
    @rm -rf $(OBJ_DIR)

$(OBJ_DIR)/%.o: %.cpp
    @mkdir -p $(OBJ_DIR)
    $(CXX) -c $< -o $@

$(OBJ_DIR)/%.o: %.c
    @mkdir -p $(OBJ_DIR)
    $(CXX) -c $< -o $@

$(BIN_DIR)/$(OUTPUT): $(OBJ)
    @mkdir -p $(BIN_DIR)
    $(CXX) -g -ansi -Wall -Werror -o $@ $(OBJ)

The result is that everything gets compiled and linked in EXCEPT the files in my VPATH directories. I suspect this may be because once my $(OBJ) prereq is being evaluated, none of the resulting objects themselves are located in the VPATH, just the source files.

Is there a better approach I can take? I do not easily have the ability to change the contents of these remote VPATH directories, because they are used by many different applications with different owners.


Solution

  • Looking at:

    CPPSRC := $(wildcard *.cpp)
    CSRC   := $(wildcard *.c)
    OBJ    := $(CSRC:%.c=$(OBJ_DIR)/%.o) $(CPPSRC:%.cpp=$(OBJ_DIR)/%.o)
    
    $(BIN_DIR)/$(OUTPUT): $(OBJ)
    

    The first line says, get all the files in my current directory that match the pattern *.cpp. The second line says, get all the files in my current directory that match the pattern *.c. The third line says, convert all these source files into object file names in the $(OBJ_DIR) directory.

    The last line says, when you want to build $(BIN_DIR)/$(OUTPUT), make sure to build all those object files first.

    Nowhere have you told make that it should build any object files for sources that it might find anywhere else (such as, for example, /one/remote/dir or /another/remote/dir). So why should it try to build them?

    You'll have to have make go looking for those files and add them to your object list.

    You could try something like this:

    VPATH = /one/remote/dir /another/remote/dir
    OBJ += $(patsubst %,$(OBJ_DIR)/%.o,$(notdir $(basename $(wildcard $(addsuffix /*.cpp,$(VPATH)) $(addsuffix /*.c,$(VPATH))))))
    

    This adds the /*.cpp and /*.c suffixes to each directory in $(VPATH), then does a wildcard to get all matching files, then takes the basename (removing the .cpp and .c suffix), then removes the directory path, then converts each base filename to prepend with $(OBJ_DIR) and add .o to the end.

    Of course, if you have two files with the same base name anywhere in your system you're out of luck and you'll have to do something more complex, preserving the directory structure for example.