Skip to content

Generic Makefiles with GCC and GNU Make – Part 2

December 2, 2014

Introduction

In the previous article on this subject (which you should read first) I looked at how to go about creating a simple generic makefile for a C++ project which allowed you to add source files to the project without modifying the makefile, and which generated dependency information for you automatically. In this article I look at expanding this makefile to work with multiple directories. Makefile sources, and example C++ projects,  can be found here, in the srcinc subdirectory.

Motivation

The simple makefile presented in the previous article wound up putting all the C++ source files and header files in a single directory together with the .o and .d intermediate compiler-produced files, and the final executables.  This organisation (or lack of it) is viable for small projects but quickly becomes hard to navigate and manage for larger ones. It’s better to split things into different directories based on the different file types – the organisation I routinely use for medium-sized C++ projects looks like this, using the program presented previously as an exemplar:

srcinc
    bin
        rollem.exe
    inc
        dice.h
        report.h
    obj
        dice.d
        dice.o
        main.d
        main.o
    src
        dice.cpp
        main.cpp
    makefile

The srcinc directory is the project root (in a real project I would probably give it the same name as the executable) and contains the makefile that builds the project. The bin directory contains the final executable program the compiler (controlled by the makefile) produces.. The source files are placed in the src directory, and the headers they #include are placed in inc.  The compile-produced intermediate .o and .d files are created by the compiler in the obj directory. From the compiler’s point of view, there’s nothing  magical about any of these names – you could call them whatever you like.

Changing the makefile

The key changes we need to make to the makefile to use this directory structure are to the rules that build the .o and the .d files. The previous makefile featured rules like this:

%.o: %.cpp
    $(CXX) $(CXXFLAGS) $(INCDIRS) -c $< -o $@

To work with different directories, we need to change the rules to say things like “to build a .o file in the obj directory, you need to compile the C++ file in the src directory”. Here’s a slightly cut down version of the new makefile:

PRODUCT := rollem.exe 
BINDIR := bin 
INCDIR := inc 
SRCDIR := src 
OBJDIR := obj 

CXX := g++ 
LINKER := g++ 
INCDIRS := -I$(INCDIR) 
CXXFLAGS := -std=c++11 -Wall -Wextra
 
SRCFILES := $(wildcard $(SRCDIR)/*.cpp) 
OBJFILES := $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(SRCFILES)) 
DEPFILES := $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.d,$(SRCFILES)) 

$(BINDIR)/$(PRODUCT): $(OBJFILES) 
    $(LINKER) $(CXXFLAGS) $^ -o $@ 

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp 
    $(CXX) $(CXXFLAGS) $(INCDIRS) -c $< -o $@ 

$(OBJDIR)/%.d: $(SRCDIR)/%.cpp 
    $(CXX) $(INCDIRS) -MM $< \
      | sed -e 's%^%$@ %' -e 's% % $(OBJDIR)/%'\ > $@

-include $(DEPFILES)

The first thing the makefile does is to specify the directory names for the project:

BINDIR := bin
INCDIR := inc
SRCDIR := src
OBJDIR := obj

As I said earlier, these names are not significant to the compiler, and can be changed to your own personal preferences.

The C++ compiler options are the same as those for the original makefile, with one exception:

INCDIRS := -I$(INCDIR)

which specifies that the compiler should look in the inc directory for user-written header files. The generation of lists of project filenames is also similar to the original:

SRCFILES := $(wildcard $(SRCDIR)/*.cpp) 
OBJFILES := $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.o,$(SRCFILES))
DEPFILES := $(patsubst $(SRCDIR)/%.cpp,$(OBJDIR)/%.d,$(SRCFILES))

except that the generated filenames are preceded by directories in which we are going to locate the files – the C++ .cpp sources going in the src directory, and the compiler-generated .o and .d files in the obj directory.

The rule to build the final executable says to output the executable file in the bin directory:

$(BINDIR)/$(PRODUCT): $(OBJFILES)
    $(LINKER) $(CXXFLAGS) $^ $(LIBS) -o $@

The rule for building object files now looks like this:

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
    $(CXX) $(CXXFLAGS) $(INCDIRS) -c $< -o $@

This says that the .o files in the obj directory are dependent on the .cpp files in the src directory, and specifies how to use the C++ compiler to build them.

The last (and most complex) rule needs to specify how to build the .d files containing the dependency information generated for us by GCC. This needs to change the lines that GCC produces which look like this:

main.o: src/main.cpp inc/dice.h inc/report.h

into lines that look like this:

obj/main.d obj/main.o: src/main.cpp inc/dice.h inc/report.h

Once again, we use the sed stream editor to perform this conversion:

$(OBJDIR)/%.d: $(SRCDIR)/%.cpp
    $(CXX) $(INCDIRS) -MM $< \
      | sed -e 's%^%$@ %' -e 's% % $(OBJDIR)/%'\
      > $@

There are a couple of things to note here – the backslashes are used to escape end-of-lines in the rule so that the compiler/sed invocation is seen by the shell as being all on one line. The ‘%’ characters are used as delimiters in the sed search-and-replace expressions because forward-slashes, which you would normally use, are needed to specify the directory paths.

Finally, we need to include the dependency files we generated:

-include $(DEPFILES)

and we are done! You can now add .cpp files to the src directory, and headers to inc, and running  make will automatically pick them up and build the object files and executable for you.

Conclusion

This article has demonstrated how to use a makefile in a project which has been split up into sub-directories. In  a future article I’ll demonstrate how to modify this makefile to build debug and release versions of the product.

Advertisements
Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: