patterncppMinor
Optimize a Makefile
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:
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 $@ $
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.
I have four types of build debug/release/coverage/size(built with size optimization)
1: At the top level your make file should just call the makefile in the source directory(s).
Some other things I define in my make file:
So
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
3: Default rules
The default rule for C++ code (source to object is)
The default rule for linking is
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.
I would change that too:
Also defining the C compiler to g++ may not always do what you want.
I would prefer to re-define the C compiler as the C++ compiler and if need be then be explicit about the C++ compiler.
5: COMPILER flags.
Here you have a fixed set.
These look fine but I would not explicitly set them I would append to the ones defined by the makefile system:
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.
The
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 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
- 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 installSome 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)/libSo
sudo make install PREFIX=/usrwill 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 -rfI would change that too:
RM = rm -rfAlso 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 = -O3The
-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-code6: 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/MakefileMy 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)/libsudo 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.