Search code examples
makefilecompilationshared-librariesprojectgnu-make

How to compile sources in different directories into object files all in a single directory (Makefile)?


I have this layout:

project/
  Makefile
  core/
    a.cpp
    a.hpp
  utl/
    b.cpp
    b.hpp
  obj/

And I would like to have all the .o files in the obj folder, so that I can then make a shared library from those object files. But since my .cpp files are in different directories I have no clue how to automate this. There are multiple directories not only these two. Any hint is appreciated.

My attempt fails because I assume that Make automatic rule for .o (which I wanted to use) wants a .cpp in the same directory where .o is supposed to be?

# grab all cpp files with their path that are in some DIRS list
SRC = $(wildcard *.cpp) $(foreach DIR,$(DIRS),$(wildcard $(DIR)/*.cpp))

# remove the path
SRC_WITHOUT_PATH = $(notdir $(SRC))

# stick the .obj/ directory before the .cpp file and change the extension
OBJ = $(SRC_WITHOUT_PATH:%.cpp=./obj/%.o)

# error is no rule to make target obj/a.o

Solution

  • You can make a shred library from object files, even if they're in different directories. So that's not really a reason to put them elsewhere.

    However, a better reason is just to keep the source directories tidy and make it simple to clean up (just remove the obj directory).

    Putting object files from source files in different directories into a single directory is problematic: if you have two source files with the same name they'll overwrite each other. One common way to work around this is to keep the directory structure for source files but put it below a new top-level directory; GNU make supports that easily:

    # grab all cpp files with their path that are in some DIRS list
    SRC = $(wildcard *.cpp) $(foreach DIR,$(DIRS),$(wildcard $(DIR)/*.cpp))
    
    # stick the .obj/ directory before the .cpp file and change the extension
    OBJ = $(addprefix obj/,$(SRC:.cpp=.o))
    
    obj/%.o : %.cpp
            @mkdir -p $(@D)
            $(COMPILE.cpp) -o $@ $<
    

    If you really, really want to have all the object files in the same directory you'll have to get fancier, because make uses simple string matching for targets so you have to write a new rule for every relationship where the target and prerequisite names are different: basically that means a new rule for every separate source directory.

    You can avoid this by using the VPATH feature of GNU make, like this:

    # grab all cpp files with their path that are in some DIRS list
    SRC = $(wildcard *.cpp) $(foreach DIR,$(DIRS),$(wildcard $(DIR)/*.cpp))
    
    # remove the path
    SRC_WITHOUT_PATH = $(notdir $(SRC))
    
    # stick the .obj/ directory before the .cpp file and change the extension
    OBJ = $(SRC_WITHOUT_PATH:%.cpp=obj/%.o)
    
    # Give all the source directories to make
    VPATH = $(sort $(dir $(SRC))
    
    obj/%.o : %.cpp
            $(COMPILE.cpp) -o $@ $<