1

I've trying to learn the "best practice" makefile's for a project.

Please review my Makefile file below and suggest changes to enhance it.

The dir layout:

root dir
---  Makefile
deps
---  deps
bin
---  binary
objs
---  all .o files
include
---  all .h files
src
---  all .c .cc files

The makefile:

#
# Generic makefile
#

all: tengine test2

#
# Include files for compiling, and libraries for linking.
#

INC=-I /usr/include -I /usr/local/include -I /usr/include/hiredis

LIB=-lhiredis

#
# Debug or not debug?
#

DEBUG=1

ifdef DEBUG
    CFLAGS=-Wall -Winline -pipe -g -DDEBUG #-pedantic -pg 
else
    CFLAGS=-Wall -Winline -pipe -O3 -march=native -funroll-all-loops \
           -finline-functions #-pedantic 
endif

#CXXFLAGS=$(CFLAGS)


# Rules for creating dependency files

deps/%.d: src/%.cc
    @echo Generating $@
    @mkdir -p $(dir $@)
    $(CXX) $(CXXFLAGS) $(INC) -MM -MT '$(patsubst src/%,obj/%,%(patsubst %.cc,%.o,$<))' $< > $@

deps/%.d: src/%.c
    @echo Generating $@
    @mkdir -p $(dir $@)
    $(CXX) $(CXXFLAGS) $(INC) -MM -MT '$(patsubst src/%,obj/%,%(patsubst %.c,%.o,$<))' $< > $@


# Rules for compilation
#
# C source with header and no c++ code

obj/%.o: src/%.c src/%.h deps/%.d
    @echo Compiling $@
    @mkdir -p $(dir $@)
    $(CC) $(CFLAGS) $(INC) -o $@ -c $<

# C++ source with header.

obj/%.o: src/%.cc src/%.h deps/%.d 
    @echo Compiling $@
    @mkdir -p $(dir $@)
    $(CXX) $(CXXFLAGS) $(INC) -o $@ -c $<

# C source without header and no c++ code

obj/%.o: src/%.c deps/%.d
    @echo Compiling $@
    @mkdir -p $(dir $@)
    $(CC) $(CFLAGS) $(INC) -o $@ -c $<

# C++ source without header.

obj/%.o: src/%.cc deps/%.d 
    @echo Compiling $@
    @mkdir -p $(dir $@)
    $(CXX) $(CXXFLAGS) $(INC) -o $@ -c $<

# ##############################################################
#
# TARGET: tengine
#
# ##############################################################

OBJS= obj/main.o obj/tengine.o 

tengine: $(OBJS)
    $(CXX) -pipe $(CXXFLAGS) -o bin/tengine $(OBJS) $(LIB)

# ##############################################################
#
# TARGET: test2
#
# ##############################################################

OBJS= obj/main.o obj/test2.o 

test2: $(OBJS)
    $(CXX) -pipe $(CXXFLAGS) -o bin/test2 $(OBJS) $(LIB)

# ##############################################################
#
# Cleanup
#
# ##############################################################

clean:
    rm -f *~ bin/* obj/* deps/* src/*~ gmon.out

help:

@echo ""
@echo "make     - builds tengine"
@echo "make test2   - builds test2"
@echo "make all     - builds tengine test2"
@echo "make clean   - deletes prior build"
amanda
  • 601
  • 2
  • 8
  • 14
  • do you really need that many section titles? –  Jan 28 '14 at 18:03
  • 1
    You probably need to change the `tengine` to `test2` in the `test2` section. I'd remove a lot of the comments in the Makefile as they are mostly noise. You should also use a macro for `g++` and for `-pipe` in the link lines. Libraries should be listed after objects if your makefiles are to be robust. You also don't seem to set CXXFLAGS but you use it. – Jonathan Leffler Jan 28 '14 at 18:16
  • Thanks. I made those changes. I added the comments for the purposes of SO, I normally do not have them in my Makefile. CXXFLAGS was commented out in the original post. – amanda Jan 28 '14 at 19:50

3 Answers3

1

If you expect other people to use your Makefile, always include a help target that prints out a message detailing the various targets that are sensible to call from the command line, and the various environment vars which can be reasonably set to do various things...

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
1

Here is a suggestion for a revised makefile, tested slightly on a 7-year-old version of Linux (RHEL 5):

# Generic makefile

TARGETS=tengine test2

all: ${TARGETS}

help:
        @echo ""
        @echo "make         - builds ${TARGETS}"
        @echo "make tengine - builds tengine"
        @echo "make test2   - builds test2"
        @echo "make clean   - deletes prior build"
        @echo "make help    - prints this help"

# Switches:

INC=-I/usr/include/hiredis
LIB=-lhiredis
SUBDIRS=obj deps bin
LNK=gcc -g -Wl,--warn-common
DEBUG=1

ifdef DEBUG
        CFLAGS=-Wall -Winline -pipe -g -DDEBUG #-pedantic -pg 
else
        CFLAGS=-Wall -Winline -pipe -O3 -march=native -funroll-all-loops \
                   -finline-functions #-pedantic 
endif

#CXXFLAGS=$(CFLAGS)

# Generic rules:

obj/%.o: src/%.c
        @echo Compiling $@
        @mkdir -p $(SUBDIRS)
        $(CC) $(CFLAGS) $(INC) -MMD -MF '$(patsubst src/%.c,deps/%.d,$<)' -o $@ -c $< 

obj/%.o: src/%.cc 
        @echo Compiling $@
        @mkdir -p $(SUBDIRS)
        $(CXX) $(CXXFLAGS) $(INC) -MMD -MF '$(patsubst src/%.c,deps/%.d,$<)' -o $@ -c $<

${TARGETS}: %:bin/%

# Specific target rules:

bin/tengine: obj/main.o obj/tengine.o
        $(LNK) $^ $(LIB) -o $@ 

bin/test2: obj/main.o obj/test2.o
        $(LNK) $^ $(LIB) -o $@ 

clean:
        rm -f *~ src/*~ gmon.out
        rm -fr $(SUBDIRS)

-include deps/*.d

Some notes:

  • A key problem with the original was that the dependency were generated, but not used. This has been fixed using -include deps/*.d (at the end).

  • Now that deps/*.d is used, the makefile doesn't need to have the src/%.h cases.

  • The original was also putting garbage into these files: in $(patsubst src/%,obj/%,%(patsubst %.cc,%.o,$<)) the third % should have been a $.

  • In the revised version, the dependencies are generated at the same time as the object, using -MMD. This is quicker, shortens the makefile, and adds some DRY.

  • Shortened INC: why bother including the standard system include directories? And in fact gcc will apparently ignore your -I /usr/include -I /usr/local/include anyway.

  • Removed your two different definitions of OBJS. Not needed, and potentially confusing. Used $^ instead.

  • It is always a good idea for make clean to completely undo everything make does, so you are left with what you started. But the sub-directories obj/ and deps/ were being created, and never deleted. Also, bin/ was pre-supposed to exist.

  • For the linking, added $(LNK), with LNK=gcc -g -Wl,--warn-common (but you may not want the warnings). AFAIK, all the other usual $(CFLAGS) are ignored for links.

  • Removed comments, which were (mostly) distracting.

  • Repeated twice make;make now gives make: Nothing to be done for ....

See also gcc dependency generation for a different output directory.

Community
  • 1
  • 1
Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77
-1

See this and that answers for examples about Makefile. Also run make -p to understand the builtin rules inside GNU make, so use $(LINK.cc) for e.g. your bin/tengine target.

For complex builds, consider upgrading to GNU make 4.0 and use its Guile ability.

You may want to generate automatically dependencies. Read automatic prerequisites and about autodependencies; read also about GCC preprocessor options like -M or -MD etc etc ....

Community
  • 1
  • 1
Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547