C++ applications are generally composed of many source files, header files, and external libraries. During the normal course of project development, source files and libraries get added, changed, or removed. During the testing/development phase, the project is recompiled and re-linked many times. To produce an executable, all changed source files must be recompiled, and the object files must all be re-linked.
Keeping track of all of the parts of such a project requires a mechanism that precisely specifies the input files involved, the tools needed to build, the intermediate targets and their dependencies, and the final executable target.
The most widely used utility for handling the job of building a project is make. It reads the details of the project specifications and the instructions for the compiler from a Makefile, which resembles a shell script but contains (at a minimum):
The make command by default loads the file named Makefile from your current working directory and performs the specified build steps (compiling and linking).
The immediate benefit of using make is that it recompiles only the files that need to be rebuilt, rather than blindly recompiling every source file every time. Figure 3.1 is a diagram that attempts to show the steps involved in building a Qt application. To learn more about make, we recommend the book The Linux Development Platform by Rafeeq Ur Rehman and Christopher Paul (Prentice Hall).
Figure 3.1. (q)make build steps
With Qt, it is not necessary to write Makefiles. Qt provides a tool called qmake to generate Makefiles for you. It is still necessary to somehow run make and understand its output. Most IDEs run make (or something similar) under the covers and either display or filter its output.
The following transcript shows which files get created at each step of the build process for Example 3.1.
src/qapp> ls -sF total 296 4 main.cpp src/qapp> qmake -project src/qapp> ls -sF total 296 4 main.cpp 4 qapp.pro src/qapp> qmake src/qapp> ls -sF total 296 4 main.cpp 4 qapp.pro 4 Makefile src/qapp> make g++ -c -pipe -g -Wall -W # compile step -D_REENTRANT -DQT_CORE_LIB -DQT_GUI_LIB -DQT_SHARED -I/usr/local/qt/mkspecs/linux-g++ -I. -I/usr/local/qt/include/QtGui -I/usr/local/qt/include/QtCore -I/usr/local/qt/include -I. -I. -I. -o main.o main.cpp g++ -Wl,-rpath,/usr/local/qt/lib # link step -L/usr/local/qt/lib -L/usr/local/qt/lib -lQtGui_debug -L/usr/X11R6/lib -lpng -lXi -lXrender -lXinerama -lfreetype -lfontconfig -lXext -lX11 -lm -lQtCore_debug -lz -ldl -lpthread -o qapp main.o src/qapp> ls -sF total 420 4 main.cpp 132 main.o 124 qapp* 8 Makefile 4 qapp.pro src/qapp>
Notice that we can see the arguments passed to the compiler when we run make. If any errors are encountered, we will see them too.
3.2.1. #include: Finding Header Files
There are three commonly used ways to #include a library header file:
#include #include "headerFile" #include "path/to/headerFile"
The angle brackets (< >) indicate that the preprocessor must look (sequentially) in the directories listed in the include path for the file.
A quoted filename indicates that the preprocessor should look for headerfile in the including file's directory first. A quoted path indicates that the preprocessor should check the path directory first. The path information can be absolute or relative (to the including file's directory). If no file is found at the specified location, the directories listed in the include path are searched for headerfile.
If versions of the file exist in more than one directory in the include path, the search will stop as soon as the first occurrence of the file has been found. If the file is not found in any of the directories of the search path, then the compiler will report an error.
For items in the C++ Standard Library, the compiler generally already knows where to find the header files. For other libraries, we can expand the search path by adding a -I/path/to/headerfile switch to the compiler.
If you use an IDE, there will be a Project->Settings->Preprocessor, or Project->Options->Libraries configuration menu that lets you specify additional include directories, which end up getting passed as -I switches to the compiler.
With qmake, as we will soon see, you can add INCLUDEPATH += dirname lines to the project file. These directories end up in the generated Makefile as INCPATH macros, which then get passed on to the compiler/preprocessor at build time.
In general, it is a good idea to #include non-Qt header files after Qt header files. Since Qt does define many symbols, in the compiler as well as the preprocessor, this helps you avoid/find name clashes more easily. |
3.2.2. The make Command
Instead of running the command line compiler directly, we will start using make,[2] which greatly simplifies the build process when a project involves multiple source files and libraries.
[2] Depending on your development environment, this program goes under many other names, such as mingw32-make, nmake, gmake, or unsermake.
We have seen before that qmake (without arguments) reads a project file and builds a Makefile. Example 3.3 is a slightly abbreviated look at the Makefile for the previous qapp project.
Example 3.3. src/qapp/Makefile-abbreviated
# Excerpts from a makefile ####### Compiler, tools and options CC = gcc # executable for C compiler CXX = g++ # executable for c++ compiler LINK = g++ # executable for linker # flags that get passed to the compiler CFLAGS = -pipe -g -Wall -W -D_REENTRANT $(DEFINES) CXXFLAGS = -pipe -g -Wall -W -D_REENTRANT $(DEFINES) INCPATH = -I/usr/local/qt/mkspecs/default -I. -I$(QTDIR)/include/QtGui -I$(QTDIR)/include/QtCore -I$(QTDIR)/include # Linker flags LIBS = $(SUBLIBS) -L$(QTDIR)/lib -lQtCore_debug -lQtGui_debug -lpthread LFLAGS = -Wl,-rpath,$(QTDIR)/lib # macros for performing other operations as part of build steps: QMAKE = /usr/local/qt/bin/qmake ####### Files HEADERS = # If we had some, they'd be here. SOURCES = main.cpp OBJECTS = main.o [snip] QMAKE_TARGET = qapp DESTDIR = TARGET = qapp # default target to build first: all # to build "first" we must build "all" ####### Implicit rules .SUFFIXES: .c .o .cpp .cc .cxx .C .cpp.o: $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $< ## Possible targets to build all: Makefile $(TARGET) # this is how to build "all" $(TARGET): $(OBJECTS) $(OBJMOC) # this is how to build qapp $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(OBJCOMP) $(LIBS) qmake: FORCE # "qmake" is a target too! @$(QMAKE) -o Makefile qapp.pro # what does it do? dist: # Another target @mkdir -p .tmp/qapp && $(COPY_FILE) --parents $(SOURCES) $(HEADERS) $(FORMS) $(DIST) .tmp/qapp/ && (cd 'dirname .tmp/qapp' && $(TAR) qapp.tar qapp && $(COMPRESS) qapp.tar) && $(MOVE) 'dirname .tmp/qapp'/qapp.tar.gz . && $(DEL_FILE) -r .tmp/qapp clean:compiler_clean # yet another target -$(DEL_FILE) $(OBJECTS) -$(DEL_FILE) *~ core *.core ####### Dependencies for implicit rules main.o: main.cpp |
The command make checks the dependencies and performs each build step specified in the Makefile. The name and location of the final result can be set with the project variables, TARGET and target.path. If TARGET is not specified, the name defaults to the name of the directory in which the project file is located. If target.path is not specified, the location defaults to the directory in which the project file is located.
3.2.3. Cleaning Up Files
make can clean up the generated files for you with the two targets clean and distclean. Observe how they are different from the following code:
src/qapp> make clean rm -f main.o rm -f *~ core *.core src/qapp> ls Makefile main.cpp qapp qapp.pro src/qapp> make distclean rm -f qmake_image_collection.cpp rm -f main.o rm -f *~ core *.core rm -f qapp rm -f Makefile src/qapp> ls main.cpp qapp.pro
After a make distclean, the only files that remain are the source files that can go into a tarball for distribution.
If you modify a project file since the last execution of make, the next invocation of make should rebuild the Makefile itself (via qmake) before re-running make on the newly generated Makefile. In other words, the Makefile is qmake-aware and can re-qmake itself. |
The command make dist will create a tarball (dirname.tar.gz) that contains all the source files that the project file knows about. |
As we add more source-code, header, or library modules for our project, we edit the .pro file and add the new items to the SOURCES, HEADERS, and LIBS lists. The same documentation standards that apply to C++ source code should be applied to project files (where comments begin with #).
We think of the project file as a map of our project, containing references to all files and locations required for building our application or library. Like other source code files, this is both human readable and machine readable. The .pro file is the first place to look when we encounter "not found" or "undefined" messages during the build process (especially at link time). For further details we recommend that you read Trolltech's guide to qmake.[3]
[3] http://trolltech.com/qmake-manual.html
Part I: Introduction to C++ and Qt 4
C++ Introduction
Classes
Introduction to Qt
Lists
Functions
Inheritance and Polymorphism
Part II: Higher-Level Programming
Libraries
Introduction to Design Patterns
QObject
Generics and Containers
Qt GUI Widgets
Concurrency
Validation and Regular Expressions
Parsing XML
Meta Objects, Properties, and Reflective Programming
More Design Patterns
Models and Views
Qt SQL Classes
Part III: C++ Language Reference
Types and Expressions
Scope and Storage Class
Statements and Control Structures
Memory Access
Chapter Summary
Inheritance in Detail
Miscellaneous Topics
Part IV: Programming Assignments
MP3 Jukebox Assignments
Part V: Appendices
MP3 Jukebox Assignments
Bibliography
MP3 Jukebox Assignments