Introduction
I will describe how to write a makefile, that can compile a qt4 program on the Raspberry Pi (2).
I will not describe, how to install all needed components (simply because I don’t have records about that and explanations on the internet were applicable. Feel free to provide that process and I will update the article :)).
The files I use for my sample are as follows:
- main.cpp // contains
qt
calls, but does not define a qt
class - numberplacedoc.cpp // pure C++ file containing program logic
- numberplacedoc.h
- numberplaceview.cpp // implementation of new
qt
class (QtMainWindow inherit
) - numberplaceview.h // definition of new qt class (
QtMainWindow inherit
) - ui_numberplaceview.h // specifies the layout of our window
Background
I need to show my programmed application to my professor. But I didn't want to carry my laptop to the university all the time (it's heavy and big). Instead, I decided to bring my rapi(2) which is very small and light. My last work was this qt
application described here (Sudoku game). Programming it was no problem but making it compile on the Raspberry Pi took a lot of time. A lot of tutorials out there seem to only be half-baked or/and my knowledge about makefiles was close to 0. So I decided to provide a fully functional makefile to work with.
Using the Code
I created the sourcefiles with qt5
, which makes it necessary to create a link inside the include/qt4 directory on the pi:
Sudo ln -s /usr/include/qt4/QtGui /usr/include/qt4/QtWidgets
Creating the makefile
Create a new file called „makefile“ inside the directory where all the sources are.
Setting Up the Basic Variables of the makefile
# often called CXX instead of COMPILER
COMPILER=arm-linux-gnueabihf-g++
# defining include directories
INCLUDEDIR = ./
INCLUDEDIR += /usr/include/qt4/
INCLUDEDIR += /usr/include/qt4/Qt
INCLUDEDIR += /usr/include/qt4/QtGui
INCLUDEDIR += /usr/include/qt4/QtCore
# tell c++ which libraries we want to use
LIBRARY += QtCore QtGui
LIBRARYDIR = /usr/lib/arm-linux-gnueabihf
LIBRARYDIR +=/usr/local/lib
XLINK_LIBDIR = /lib/arm-linux-gnueabihf
XLINK_LIBDIR += /usr/lib/arm-linux-gnueabihf
#XLINK_LIBDIR += /usr/local/lib/
INCDIR = $(patsubst %,-I%,$(INCLUDEDIR))
LIBDIR = $(patsubst %,-L%,$(LIBRARYDIR))
LIB = $(patsubst %,-l%,$(LIBRARY))
XLINKDIR = $(patsubst %,-Xlinker -rpath-link=%,$(XLINK_LIBDIR))
# make sure we use c++11 standard!
CPPSTD = -std=c++11
OPT = -O0
DEBUG = -g
WARN= -Wall
PTHREAD= -lpthread
# further linker options you might want to add.
# if you make changes here, make sure to add them to
# SPECIAL_LINK_OPTIONS below.
#GPIO = -lwiringPi
#QTGUI = -lQtGui
#QTNETWORK = -lQtNetwork
#QTCORE = -lQtCore
#QT += core gui widget
#QT += opengl
SPECIAL_LINK_OPTIONS = #$(GPIO)
#set up the compile statement (flags often called CXXFLAGS)
COMPILE_FLAGS = $(OPT) $(DEBUG) $(WARN) $(INCDIR) $(CPPSTD)
COMPILE = $(COMPILER) $(COMPILE_FLAGS) -c
# set up link statement (flags often called LDLFLAGS)
LINK_FLAGS= $(LIBDIR) $(LIB) $(XLINKDIR) $(PTHREAD) $(SPECIAL_LINK_OPTIONS)
LINK = $(COMPILER) $(LINK_FLAGS)
EXECUTABLE = executable
Building Everything by Hand
First, we will compile everything by hand to get a better feeling of what is happening. If you copy-paste this code, then make sure to have TABS before the $(LINK)
and $(COMPILE)
statements. Spaces do not count.
Note for beginners: makefile targets
Think of a target like a „case
“ inside a switch
-case
statement. The syntax is as follows:
#########################################
####### START OF COMPILE STATEMENTS ########
#make shorter names to work with
NPD=numberplacedoc
NPV=numberplaceview
OBJ_FILES = $(NPD).o $(NPV).o main.o $(NPV).moc.o
# put everything together
all: $(OBJ_FILES)
$(LINK) $(OBJ_FILES) -o $(EXECUTABLE)
# usual compile of pure c++ file
$(NPD).o: $(NPD).cpp $(NPD).h
$(COMPILE) $(NPD).cpp
# usual compile of code using qt (calling qt functions)
main.o: main.cpp
$(COMPILE) main.cpp
# usual compile of code implementing a qt class
$(NPV).o: $(NPV).cpp $(NPV).h
$(COMPILE) $(NPV).cpp
# usual compile of generated cpp file
$(NPV).moc.o: $(NPV).moc.cpp
$(COMPILE) $(NPV).moc.cpp
# this is the heart: create a new cpp file through the qt4 middle compiler
$(NPV).moc.cpp: $(NPV).h
moc-qt4 $(NPV).h -o $(NPV).moc.cpp
######## END OF COMPILE STATEMENTS #########
#########################################
To get a better view of the hierarchy, let's look at this picture:
Building
Now typing in:
make
should build your executable ready to execute.
Errors
If you get „strange“ errors like:
- undefined reference to
QApplication
- virtual call inside the
ctor
and dtor
Or other strange C++ errors that somehow refer to qt
, make sure:
- numberplaceview.moc.cpp is correctly compiled
- numberplaceview.moc.o does exist
- numberplaceview.moc.o and is inside the linking list
Please provide feedback here for ANY error you encounter, so we can list and solve them here.
Explanation
Basically everything is as if you have a pure C++ program. Only, we create one special new cpp file, that is created by the qt4
compiler, that needs to go into the linklist. This is the whole magic of the building process.
Important note: If this file is not in the list, we will get some strange Errors.
Also note that we didn’t care about ui_numberplaceview.h in our building process.
Generalize (Practicalize) the Building Command
I hope you now have an idea of what is going on throughout the building process.
Now, nobody wants to list their files like this all the time they start a new project. For that purpose, we’ll generalize the building command so the makefile
grabs all necessary files by itself (through patterns inside the filenames).
Note: The provided makefile is searching for sources only in the current directory.
First, we change the path of our result, to make it better customizable:
OUTPUT_PATH = ./
EXECUTABLE := $(OUTPUT_PATH)executable
We want an intermediate directory to not pollute our folder with object files:
TMP_DIR=tmp/
We need to define files, that qt
needs to generate. For better understanding purposes, I call only one command per line.
I use the pattern *view.h
or qt*.h
to determine files, that need to be generated. That means, if you want to use this automation, your files, that need to be qt
-compiled should have this pre- or suffix. Feel free to change the pattern or add files manually.
# find out which files we need to midl-compile with qt
# Feel free to add specific files, that don't follow the pattern
H_FILES_FOR_QT := $(wildcard *view.h)
H_FILES_FOR_QT += $(wildcard qt*.h)
# remove all gui files generated by qt (they don't need to be midl-compiled)
H_FILES_FOR_QT := $(filter-out $(wildcard ui_*), $(H_FILES_FOR_QT))
Now we’re ready to tell, which files we actually need:
# finally: defining all files we need to generate
CPP_FILES_TO_GEN := $(H_FILES_FOR_QT:.h=.moc.cpp)
Make removes „intermediate“ files automatically. But I want to keep them, so we can partial rebuild better, so:
# prevent .moc-files being removed by make automatically
.SECONDARY: $(CPP_FILES_TO_GEN)
Put all lists together and create a big list of all cpp-files, that want to be compiled:
# recognizing everything, that needs to be compiled
# ---------------------------------------------------
CPP_FILES := $(wildcard *.cpp) $(CPP_FILES_TO_GEN)
Same goes for object files. But we want them inside our tmp-dir. So add that to their path:
# defining all object files needed (inside intermediate dir)
# -------------------------------------------------------------
OBJECT_FILES := $(addprefix $(TMP_DIR),$(CPP_FILES:.cpp=.o))
Note for beginners: @-sign
I use the „@
“-sign to suppress the command itself, so we get a much more pretty output.
We want to make sure our TMP_DIR
does exists before object files get created there.
# defining the linking process
# ---------------------------------
all: $(TMP_DIR) linkAll
@echo ===============================
@echo ==== build successful ========
@echo ===============================
# make sure we have an intermediate directory
$(TMP_DIR):
@echo create $(TMP_DIR) for intermediate files
@mkdir $(TMP_DIR)
For linking actually, we need the TMP_DIR
, but it is not a prerequisite here. The reason for that is, that
linkAll: $(OBJECT_FILES)
@echo
@echo ===============================
@echo === compiling finished ========
@echo ===============================
@echo
@echo Linking...
@$(LINK) $? -o $(EXECUTABLE)
This is the heart of the compiling process. If a prerequisite „orders“ an .o-file or moc.cpp, it will get cooked here. .o-files can only be ordered within the TMP_DIR. moc.cpp-files are ordered where all sources are.
# for any object file that is needed (specified above) build it from the cpp
# making output more pretty by using @
$(TMP_DIR)%.o: %.cpp
@echo compiling: $< to $@
@$(COMPILE) $< -o $@
# for any needed .moc.cpp file: build it with moc-qt4 from the header
%.moc.cpp: %.h
@echo creating moc: $@
@moc-qt4 $< -o $@
Defining which files should be cleaned up in case of a new build:
# defining cleanup process
# ------------------------------------
# if "clean" exists as a file, this code will break, because
# target always specify files. So don't create a file called "clean" :)
# call: "make clean" for using this:
clean:
@echo remove intermediate files in: $(TMP_DIR)
@rm -f $(TMP_DIR)*
@echo remove midl-compiler files from qt
@rm -f $(CPP_FILES_TO_GEN)
@echo remove $(EXECUTABLE)
@rm -f $(EXECUTABLE)
Mentionable Sources
- The tutorial I was using:
- Explanations about "crazy" commands within a makefile (like
$<
)
- A video of my raspi case (whoever is interested, how "small" and "light" it is)
History
- 30th May, 2017: Initial version