HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Optimize a Makefile

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
makefileoptimizestackoverflow

Problem

Since my project is getting bigger every day and I am just a starter in the wonderful world of makefiles, I need some help improving mine because, although it works (almost) as I wish, it really started looking like a mess. So it would be nice if someone could help me with it (and of course, advice is welcome).

Basically this is the structure of my C++ project:

myProject
| doc/ (nothing to do here)
| obj/ (where all *.o go)
| src/ (where I have all my .h and .cpp)
| tests/ (where all my tests are)


I have to say, in my Makefile I have some normal stuff, but also some really ugly stuff, so I hope you do not panic:

`define \n

endef

EXECUTABLE = main

# compiler
CC = g++
CFLAGS = -g -std=gnu++0x -Wall -Wno-reorder -I. $(SYSTEMC_INCLUDE_DIRS)
LFLAGS = $(SYSTEMC_LIBRARY_DIRS)
FINAL = -o $(EXECUTABLE)
LIBS = -lsystemc-ams -lsystemc | c++filt

# directory names
SRCDIR = src
OBJDIR = obj
TSTDIR = tests

SOURCES := $(wildcard $(SRCDIR)/*.cpp)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
TEST_SRC := $(wildcard $(TSTDIR)/*.cpp)
OBJECTS := $(SOURCES:$(SRCDIR)/%.cpp=$(OBJDIR)/%.o)
TESTS := $(TEST_SRC:$(TSTDIR)/%.cpp=$(TSTDIR)/%.o)
rm = rm -rf

all: $(EXECUTABLE)

check: testbenches run_test_script

debug: CC += -O0 -fno-inline
debug: all

main: createdir maincpp $(OBJECTS)
$(CC) $(CFLAGS) $(LFLAGS) $(FINAL) $(OBJDIR)/$@.o $(OBJECTS) $(LIBS)

TBS = $(basename $(TEST_SRC))

testbenches: createdir $(OBJECTS) $(TESTS)
$(foreach tb, $(TBS), $(CC) $(CFLAGS) $(LFLAGS) -o $(tb).tst $(tb).o $(OBJECTS) $(LIBS) ${\n})

run_test_script:
@cd $(TSTDIR); \
./run_tests.sh

createdir:
@mkdir -p obj

maincpp:
$(CC) $(CFLAGS) -c -o $(OBJDIR)/main.o main.cpp

$(TESTS): $(TSTDIR)/%.o : $(TSTDIR)/%.cpp
$(CC) $(CFLAGS) -c -o $@ $

Solution

A couple of things I dislike about your set up.

  • Your low level make file in the top level directory.



  • You only have one object directory (so you can only have one type of build)



I have four types of build debug/release/coverage/size(built with size optimization)

  • You use explicit commands where the makefile internal rules will work just as well.



1: At the top level your make file should just call the makefile in the source directory(s).

# (you can have a target for all the commands you support)
  # (I use the following as my starting base)
  all:
       $(MAKE) -C src
  clean:
       $(MAKE) -C src clean
  debug:
       $(MAKE) -C src debug
  release: 
       $(MAKE) -C src release
  size:
       $(MAKE) -C src size
  test:
       $(MAKE) -c src test
  veryclean:
       $(MAKE) -C src veryclean
  install:
       $(MAKE) -C src install


Some other things I define in my make file:

#
  # Basic block for building.
  # ?= define if not set on command line.
  ROOT    ?= $(shell dirname `pwd`)
  BUILD   ?= debug

  #
  # Set up SRC and OBJ directories.
  # Build debug/release/size and coverage into different directories.
  SRC_DIR = $(ROOT)/src
  OBJ_DIR = $(ROOT)/$(BUILD)

  #
  # Install by default done locally
  # but you do want to be able to install into the standard locations.
  PREFIX        ?= $(ROOT)
  PREFIX_BIN    ?= $(PREFIX)/bin
  PREFIX_INC    ?= $(PREFIX)/include
  PREFIX_LIB    ?= $(PREFIX)/lib


So

sudo make install PREFIX=/usr


will install the code into standard locations in the OS (or /usr/local if you prefer).

2: Object files of different types can not be linked together.

You must use the exact same set of flags on every object file to guarantee they are binary compatible. So I build debug and release versions of the executables into different object directories. That way when linking there is no possibility of accidentally mixing objects of different types.

3: Default rules

The default rule for C++ code (source to object is)

%.o: %.cpp
    $(CXX) -c $^ $(CPPFLAGS) $(CXXFLAGS)


The default rule for linking is

%: %.o
   $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS)


I would adapt these rather than making your own set of variable names.

4: Commands

Standard conventions means that variables should be in all caps.

rm  = rm -rf


I would change that too:

RM  = rm -rf


Also defining the C compiler to g++ may not always do what you want.

CC       = g++


I would prefer to re-define the C compiler as the C++ compiler and if need be then be explicit about the C++ compiler.

# CXX  = Define if needed. Defaults to the correct system compiler.
CC     = $(CXX)


5: COMPILER flags.

Here you have a fixed set.

CFLAGS   = -g -std=gnu++0x -Wall -Wno-reorder -I. $(SYSTEMC_INCLUDE_DIRS)


These look fine but I would not explicitly set them I would append to the ones defined by the makefile system:

CFLAGS   += -g -std=gnu++0x -Wall -Wno-reorder -I. $(SYSTEMC_INCLUDE_DIRS)
    ##  ^^^^^ Add my flags onto the default ones.


Also your flags include all things all the time. I would divide this up to define the flags based on the type of build you are doing.

CFLAGS   += $(CFLAGS_$(BUILD)) -std=gnu++0x -Wall -Wno-reorder -I. $(SYSTEMC_INCLUDE_DIRS)
CFLAGS_debug    = -g
CFLAGS_release  = -O3


The -Wall is a good starting point. But it is by no way All the warning flags (just a small subset). I personally use a few more:

-Wall -Wextra -Wstrict-aliasing -ansi -pedantic -Werror -Wunreachable-code


6: The last thing I do is encapsulate all my rules in a generic makefile.

Thus each project makefile only defines exactly what I need (and then includes the generic makefile). That way it is easy to see what I am actually building (and mistakes only need to be corrected in one place).

Example of Generic makefile

# My generic make file depends on this environment variable
THORSANVIL_ROOT         = $(realpath ../)

# This is what I want to build.
# My tools support a couple of extensions
#    app:    An executable.
#    slib:   A shared lib
#    a:      A static lib
#    dir:    calls make -C 
#    head:   A header only C++ library
TARGET                  = Serialize.slib

#
# Generic extension applied to all files. extensions.
LINK_LIBS               = Json
UNITTEST_LINK_LIBS      = Json
FILE_WARNING_FLAGS      += -Wno-overloaded-virtual

#
# Specific extensions applied to all this source file
JsonSerilizeVardacTest_CXXFLAGS += -pedantic -Werror

#
# Now include the generic make file
# With all the rules I have built up.
include ${THORSANVIL_ROOT}/build/tools/Makefile


My generic build file can be found here. Have a look and take what you find useful.

7: Plug for things I do but thats because I am ecentric.

The one thing I hate about make fils is the long lines it prints when building. These are useless and just confuse the output.

```
g++ -c URILexer.cpp -o debug/URILexer.o -fPIC -Wall

Code Snippets

# (you can have a target for all the commands you support)
  # (I use the following as my starting base)
  all:
       $(MAKE) -C src
  clean:
       $(MAKE) -C src clean
  debug:
       $(MAKE) -C src debug
  release: 
       $(MAKE) -C src release
  size:
       $(MAKE) -C src size
  test:
       $(MAKE) -c src test
  veryclean:
       $(MAKE) -C src veryclean
  install:
       $(MAKE) -C src install
#
  # Basic block for building.
  # ?= define if not set on command line.
  ROOT    ?= $(shell dirname `pwd`)
  BUILD   ?= debug

  #
  # Set up SRC and OBJ directories.
  # Build debug/release/size and coverage into different directories.
  SRC_DIR = $(ROOT)/src
  OBJ_DIR = $(ROOT)/$(BUILD)

  #
  # Install by default done locally
  # but you do want to be able to install into the standard locations.
  PREFIX        ?= $(ROOT)
  PREFIX_BIN    ?= $(PREFIX)/bin
  PREFIX_INC    ?= $(PREFIX)/include
  PREFIX_LIB    ?= $(PREFIX)/lib
sudo make install PREFIX=/usr
%.o: %.cpp
    $(CXX) -c $^ $(CPPFLAGS) $(CXXFLAGS)
%: %.o
   $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS)

Context

StackExchange Code Review Q#45478, answer score: 4

Revisions (0)

No revisions yet.