Makefiles, what are you doing with them? 13

Posted by Brett Schuchert Wed, 27 Jan 2010 15:02:00 GMT

Half a year ago, I took some exercises written in Java and ported them to C++. At that time I punted on writing makefiles and decided to reply on an IDE (Visual Studio). That worked fine but recently we almost had a customer that was going to take this class and, bless their heart, use the command line.

Turns out they are going to use visual studio, so I can shelve this, but I figured I’d get feedback on what I have created for a makefile. Since I have not written a makefile for 12ish years, I figured things have changed. They have, but I suspect they have even more.

What follows are my environment assumptions and the makefile. I’d appreciate any suggestions.

FWIW, I wrote this makefile because it turns out fully building all files takes longer than just building what has changed. I know this seems obvious, but it is not always the case with small projects like this. There is some point where the project becomes big enough to warrant dependency-based building. The exercise meets that criteria. Sometimes the time taken to evaluate build changes is enough overhead that it can be safely removed in lieu of a full build. This is a consideration when creating custom build files for legacy-based refactoring efforts.

Environment
  • Generic Unix
  • zsh – though any shell will do really
  • g++
  • gnu make
  • This source base will still be used in Visual Studio but not this makefile
  • CppUTest used for unit testing tool
  • Provide default location for CppUTest, which can be overridden by environment variable
  • Boost library installed in /usr/local/include and /usr/local/lib (reasonable assumption or should I go ahead and allow this to be configured easily in the makefile – what’s your opinion?)
  • Project is small enough that all headers and source files are in the same directory (should I build the .o’s and put them in another directory to hide them?)
Current Makefile
ifdef CPP_U_TEST
    CppUTest_base = $(CPP_U_TEST)
else
    CppUTest_base = $(wildcard ~/src/cpputest)
endif

LIB_DIRS = -L$(CppUTest_base)/lib
INC_DIRS = -I$(CppUTest_base)/include
LIBS = -lCppUTest -lboost_date_time -lboost_regex -lboost_thread
CFLAGS = -g $(INC_DIRS) 
LDFLAGS = $(LIB_DIRS) $(LIBS)
CC = g++
SRCS := $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
OUT = run_all_tests
DEP_DIR = dependencies

all: $(OBJS)
    $(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $(OBJS)

clean:
    @rm -rf $(DEP_DIR) $(OBJS) $(OUT)

-include $(SRCS:.cpp=$(dep_dir)/.d)

.cpp.o:
    @if [ ! -d $(DEP_DIR) ] ; then mkdir $(DEP_DIR) ; fi
    $(CC) -c $(CFLAGS) -o $@ $< 
    @$(CC) -MM $(CFLAGS) $*.cpp > $(DEP_DIR)/$*.d
Comments

Leave a response

  1. Avatar
    Ben about 2 hours later:

    We’ve moved to CMake, rather than maintaining Makefiles and vcproj files simultaneously.

  2. Avatar
    kbob about 2 hours later:

    I did a little exploratory project on makefiles a year ago. The goal was to build a makefile that (a) avoids recursive make (see Recursive Make Considered Harmful, http://miller.emu.id.au/pmiller/books/rmch/ ), and (b) minimizes the per-source and per-target noise.

    Check it out here. There’s a sample project included. git://gitorious.org/better-makefiles/mainline.git

    Try “make help” in the sample project directory.

  3. Avatar
    James about 3 hours later:

    I’m no expert, but I like the makefiles we put together for CppUTest. The idea is to setup the inputs and include a helper that has the common parts for all similar makefiles.

    #Set this to @ to keep the makefile quiet
    SILENCE = @
    
    #---- Outputs ----#
    COMPONENT_NAME = Demo
    TARGET_LIB = \
        lib/lib$(COMPONENT_NAME).a
    
    TEST_TARGET = \
        $(COMPONENT_NAME)_tests
    
    #--- Inputs ----#
    PROJECT_HOME_DIR = .
    ifeq "$(CPPUTEST_HOME)" "" 
        CPPUTEST_HOME = ../CppUTest
    endif
    
    #CFLAGS are set to override malloc and free to get memory leak detection in C programs
    CFLAGS = -Dmalloc=cpputest_malloc -Dfree=cpputest_free
    CPPFLAGS = -Wall
    #GCOVFLAGS = -fprofile-arcs -ftest-coverage
    
    #includes for all compiles
    #a list of -I's are created from these directories
    
    INCLUDE_DIRS =\
      .\
      include\
      include/*\
      $(CPPUTEST_HOME)/include/
    
    #SRC_DIRS is a list of source directories that make up the target library
    SRC_DIRS = \
        src\
        src/*
    
    #TEST_SRC_DIRS is a list of directories including 
    # - A test main (AllTests.cpp by convention)
    # - OBJ files in these directories are included in the TEST_TARGET
    TEST_SRC_DIRS = \
        tests \
        tests/*
    
    # - OBJ files in these directories are included in the TEST_TARGET
    MOCKS_SRC_DIRS = \
        mocks
    
    #Flags to pass to ld
    LDFLAGS +=
    LD_LIBRARIES += -lstdc++
    
    include $(CPPUTEST_HOME)/build/ComponentMakefile
    
  4. Avatar
    Markus Gaertner about 3 hours later:

    I see three problems in the Makefile as it is: 1. $(wildcard ~/src/cpputest) does not include a wildcard at all. So something like $(HOME)/src/cpputest makes more sense to me, leaving out the wildcard function completely. 2. In the -include statement the $(dep_dir) is in lower case, while it is defined in upper case. I think this causes you the problem with the dependencies. 3. There does not seem to be a .PHONY rule to prevent make to try to build all or clean all the time. .PHONY rules depict names of rules which don’t have a physical file or directory on the hard-drive later. Make checks changedates to determine whether to rebuild or not on non-PHONY targets. 4. There last rule ”.cpp.o” does not make sense to me. %.cpp.o would, but as it is, I don’t think it would work.

    You may want to take a look into makedepend. The source dependencies are here tracked manually in a separate directory. makedepend puts these behind the Makefile. Both approaches have pros and cons. If the dependencies do not change often, then makedepend makes sense, otherwise maybe not.

    The larger problem I have with the structure as it is, is that it seems to be complicated for me. There are some tidyings I would want to apply, like introducing a depend target to trace down the dependencies. The last target looks very dubious to me. In order to build dependencies incrementally one would need to track dependencies in a depend directory, and have objects rebuilt based on the particular changes in the source files.

  5. Avatar
    Fabian Ritzmann about 4 hours later:

    I would also recommend CMake. After 20+ years of make, it is time to evolve.

    Another tool I like for small Makefiles and even general purpose scripting is Rake. IMHO, it is the better Ruby for any script that operates on files. For a bigger system and possibly heterogeneous software, I would prefer CMake however.

  6. Avatar
    Angry Poodle about 5 hours later:

    One possibility is Boost Jam (BJam) with distcc.

  7. Avatar
    James Martin about 5 hours later:

    For small stuff I use a slightly modified version of the cpputest makefile, that James pasted above.

    For big dependency riddled stuff I like http://www.scons.org/

  8. Avatar
    Jason Y 1 day later:

    This brings back memories of my college days, before I knew to go to Google for everything I don’t know first. It seems the general recommendation of the instructors was to use the lowest-tech, most arcane tools available.

  9. Avatar
    Amal Pillai 1 day later:

    The experience with CMake (www.cmake.org) on Windows and Linux (using Boost and CppTest) was good enough to go for a complete switch.

  10. Avatar
    Patrick 2 days later:

    One tip: use ”:=” instead of ”= unless you have a really good reason to prefer deferred evaluation, or you need to support ancient versions of make. If you use” as your default, then “make -np” suddenly becomes a useful makefile debugging tool.

  11. Avatar
    Patrick 2 days later:

    One tip: use ”:=” for variable assignment unless you have a really good reason to prefer deferred evaluation, or you need to support ancient versions of make. If you use ”:=” as your default, then “make -np” suddenly becomes a useful makefile debugging tool.

    (Sorry about the formatting bug in my previous version of this post…)

  12. Avatar
    Mark 3 days later:

    I would highly recommend autotools, even for small projects (more than 3 source files). Its less arcane than writing your own makefile, with the most arcane parts being more or less boilerplate. It ends up being quite easy to maintain as the project grows and needs change, and certainly much easier than maintaining a makefile.

  13. Avatar
    Donnie 4 days later:

    I was positively amused after reading this text . After all, if you need to gain knowledge about C++ or obtain a perfect essay associated with it, visit online essay writers and make a request.

Comments