patterncppModerate
Improving Makefile and general C++ project structure
Viewed 0 times
makefilegeneralprojectstructureandimproving
Problem
I am a single developer on a numerics project. As it is growing, I would like to improve
As it stands now I have a few classes (where a few depend on each other) and a
I am working under Linux using gnu make and g++ compiler.
What bothers me is:
What I have is the following:
Dir Tree
Makefile
```
workdir:=$(shell pwd)
SRCS = $(workdir)/src/main.cc \
$(workdir)/src/class1.cc \
$(workdir)/src/class2.cc \
...
INCLUDE = -I$(workdir)/include/\
-I$(workdir)/src/
CTALL = $(workdir)/include/*\
$(workdir)/src/*
CTAGS = ctags
DEL = /bin/rm -f
RESULT = projectname
CC = g++
LIBS= -lm -lboost_timer -lboost_filesystem -lboost_system
OPT = -O3 -Wall -ggdb -D__STDC_FORMAT_MACROS -std=c++0x -fipa-matrix-reorg
all: clean $(RESULT) ctags
$(RESULT): $(SRCS)
$(CC) $(LIBS
- the Makefile
- the general organisation of the project
- structuring of the files
- workflow
As it stands now I have a few classes (where a few depend on each other) and a
main.cc. In total maybe 20 files. From that the executable is build via make and ./projectName creates data files, which are later processed with gnuplot script files. This whole process can also be compressed in a shell script run.sh.I am working under Linux using gnu make and g++ compiler.
What bothers me is:
- As I add new classes
maketakes more and more time.
- Everything is build from scratch each time
makeruns, even if only one class file is changed.
- Related to that I have seen others using a build/ directory where object files files are stored. I haven't used .obj files so far. Does that solve the problem of (2.) and if yes how do I create them using make?
- A debug and release build would be nice too, but I have seen questions on that on Stack Overflow, so I'll get to that.
What I have is the following:
Dir Tree
ProjectName
|+data/ <-- data files generated by program
|+scripts/ <-- scripts that handle data processing
|~includes/
| |-headerClass1.h
| |-headerClass2.h
| |-...
| |-main.h
| |-parameters.h
|~src/
| |-main.cc
| |-class1.cc
| |-class2.cc
| |...
|-Makefile
|-tags
|-projectname <--executableMakefile
```
workdir:=$(shell pwd)
SRCS = $(workdir)/src/main.cc \
$(workdir)/src/class1.cc \
$(workdir)/src/class2.cc \
...
INCLUDE = -I$(workdir)/include/\
-I$(workdir)/src/
CTALL = $(workdir)/include/*\
$(workdir)/src/*
CTAGS = ctags
DEL = /bin/rm -f
RESULT = projectname
CC = g++
LIBS= -lm -lboost_timer -lboost_filesystem -lboost_system
OPT = -O3 -Wall -ggdb -D__STDC_FORMAT_MACROS -std=c++0x -fipa-matrix-reorg
all: clean $(RESULT) ctags
$(RESULT): $(SRCS)
$(CC) $(LIBS
Solution
Common Conventions
Building issues
It rebuilds everything every time for two reasons.
The
RESULT is dependent on SRC and rebuilds the executable from scratch each time. What you need to do is depend on OBJ and then you only need to link the objects together to build the executable. Each object will have its own dependency and only be re-built if the source file changes.
Note: Any common commands should follow the conventions set by the implicit rules:
The implicit rule for linking is:
You have a slightly more complex case but I would thus make my link command:
In your case you have a set of extra libs I would then add them as follows
The default rule for building
Your header files inclusion is part of the pre-processor stage so should be added to the
Now your object files will build as you expect.
But normally you don't want to build your object files into the same directory as your source files. This is because you can have different
Personally I build release into a directory called
Say I am building the lib
This way I can install both normal and debug version of a library and explicitly link other code against them.
Anyway back to your make file. To achieve this I normally do
Organization of files.
I normally put header and source files into the same directory. And I normally break these into directories based on libraries or executable.
But I add an install target into to my makefile that uploads the
This way if I am working on a project I can leave it (potentially in mid modification) and work on another library without the two being affected. The two will only ever interact after they have been installed into the build directory.
This means I also add the following too my makefile
Now if project
- The C++ compiler is named CXX
- What you call RESULT is usually named TARGET
- In addition to SRC you probably need OBJ
- Commands are usually replaced by uppercase version varaiable names that name themselves.
- ie. RM not DEL
Building issues
It rebuilds everything every time for two reasons.
The
all: rule performs a clean: before it starts. So obviously it has to rebuild everything:all: clean $(RESULT) ctags
# ^^^^^^ Get rid of thisRESULT is dependent on SRC and rebuilds the executable from scratch each time. What you need to do is depend on OBJ and then you only need to link the objects together to build the executable. Each object will have its own dependency and only be re-built if the source file changes.
Note: Any common commands should follow the conventions set by the implicit rules:
The implicit rule for linking is:
$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)You have a slightly more complex case but I would thus make my link command:
$(RESULT): $(OBJ)
$(CXX) $(LDFLAGS) -o $@ $(OBJ) $(LOADLIBES) $(LDLIBS)In your case you have a set of extra libs I would then add them as follows
LDLIBS += -lm -lboost_timer -lboost_filesystem -lboost_system
# ^^^^ Notice the +=
# This makes it easy to add future enhancements to the makefileThe default rule for building
*.o files from C++ files is:$(CXX) $(CPPFLAGS) $(CXXFLAGS) -cYour header files inclusion is part of the pre-processor stage so should be added to the
CPPFLAGS macro.CPPFLAGS += $(INCLUDE)Now your object files will build as you expect.
But normally you don't want to build your object files into the same directory as your source files. This is because you can have different
build versions. eg you can have a debug build and a release build. You should never mix and match object from different builds. In fact no compiler manufacturer makes any guarantees about object files built with different compiler flags. So any differences in compiler flags can potentially make the object files incompatible. As a result build your object files into different sub directories based on the type of build.Personally I build release into a directory called
release and debug into a directory called debug. I know very boring. I also suffix any targets with their type.Say I am building the lib
plop.libplop..so // release version of plop
libplopD..so // debug version of plop
libplopS..so // Single threaded version (only build this if there is a specific difference for a single threaded version of the library that can be exploited (rare))
libplopSD..so // Single threaded debug versionThis way I can install both normal and debug version of a library and explicitly link other code against them.
Anyway back to your make file. To achieve this I normally do
OBJ = $(patsubst %.cc,$(BUILD)/%.o, $(SRC))
BUILD ?= .
TARGET = projectname$(BUILD_EXT)
# BUIlD_EXT is the suffix I place on executable to tell if it is debug/relese and version information
# BUILD defines the build type. If this is not specified an unadulterated
# build is done into the current directory (rather than debug/release)
# You can do this by directory building target `make target`
## You can add build specific flags like this
## Set BUILD_EXT using the same technique.
CXX_BUILD_FLAGS_debug = -g
CXX_BUILD_FLAGS_release = -O3
CXXFLAGS += $(CXX_BUILD_FLAGS_$(BUILD))
all: debug ctags
# by default build debug version
# if you want release you must explicitly ask for it
debug:
$(MAKE) BUILD=debug target
release:
$(MAKE) BUILD=release target
target: $(TARGET)
$(TARGET): $(OBJ)
$(CXX) $(LDFLAGS) -o $@ $(OBJ) $(LOADLIBES) $(LDLIBS)
$(BUILD)/%.o: %.cc
$(CXX) $^ -c -o $@ $(CPPFLAGS) $(CXXFLAGS)Organization of files.
I normally put header and source files into the same directory. And I normally break these into directories based on libraries or executable.
But I add an install target into to my makefile that uploads the
binary and required header files to a build directory for other projects so they can use it. # Note this is not real makefile (pseudo make)
# It is designed to show intention not real make instructions.
install:
$(generate_source_control_tag)
$(CP) $(TARGET) $(BUILD)/bin # used for executables
$(CP) $(TARGET) $(BUILD)/lib # used for libs
$(CP) $(TARGET_INCLUDES) $(BUILD)/include/$(TARGET_BASE)/This way if I am working on a project I can leave it (potentially in mid modification) and work on another library without the two being affected. The two will only ever interact after they have been installed into the build directory.
This means I also add the following too my makefile
CPPFLAGS += -I$(BUILD)/include
LDFLAGS += -L$(BUILD)/libNow if project
B wants to use the library from project A. Then the convention becCode Snippets
all: clean $(RESULT) ctags
# ^^^^^^ Get rid of this$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)$(RESULT): $(OBJ)
$(CXX) $(LDFLAGS) -o $@ $(OBJ) $(LOADLIBES) $(LDLIBS)LDLIBS += -lm -lboost_timer -lboost_filesystem -lboost_system
# ^^^^ Notice the +=
# This makes it easy to add future enhancements to the makefile$(CXX) $(CPPFLAGS) $(CXXFLAGS) -cContext
StackExchange Code Review Q#15724, answer score: 11
Revisions (0)
No revisions yet.