为在 Raspberry Pi 上使用 qt(4) 编写 makefile
你想编译使用 qt 的 C++ 代码在树莓派上吗?以下是如何做到。
引言
我将描述如何编写一个 Makefile,可以在树莓派 (2) 上编译 qt4 程序。
我**不会**描述如何安装所有必需的组件(仅仅是因为我没有这方面的记录,并且网上的解释已经足够了。请随意提供这个过程,我将更新这篇文章 :))。
我使用的示例文件如下
- main.cpp // 包含
qt
调用,但未定义qt
类 - numberplacedoc.cpp // 纯 C++ 文件,包含程序逻辑
- numberplacedoc.h
- numberplaceview.cpp // 新
qt
类的实现(继承QtMainWindow
) - numberplaceview.h // 新 qt 类的定义(继承
QtMainWindow
) - ui_numberplaceview.h // 指定窗口的布局
背景
我需要向我的教授展示我编写的应用程序。但我不希望一直带着我的笔记本电脑去大学(它又重又大)。因此,我决定带上我的 rapi(2),它非常小巧轻便。我最后的工作是这里描述的 qt
应用程序(数独游戏)。编写它不成问题,但让它在树莓派上编译却花费了很长时间。网上很多教程似乎都只是半成品,并且/或者我对 makefile 的了解接近于 0。所以我决定提供一个功能齐全的 makefile 来使用。
Using the Code
我使用 qt5
创建了源文件,这使得有必要在树莓派上的 include/qt4 目录中创建一个链接。
Sudo ln -s /usr/include/qt4/QtGui /usr/include/qt4/QtWidgets
创建 Makefile
在包含所有源文件的目录中创建一个名为“makefile”的新文件。
设置 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
手动构建一切
首先,我们将手动编译所有内容,以便更好地了解正在发生的事情。如果您复制粘贴此代码,请确保在 $(LINK)
和 $(COMPILE)
语句前有 TAB 键。空格无效。
新手提示:makefile 目标
将目标想象成 switch
-case
语句中的“case
”。语法如下
#########################################
####### 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 #########
#########################################
为了更好地了解层次结构,让我们看看这张图
构建
现在输入
make
应该会构建好您的可执行文件,随时可以运行。
错误
如果您收到“奇怪”的错误,例如
- 未定义的引用
QApplication
- 在
ctor
和dtor
中进行虚调用
或者其他任何与 qt
相关的奇怪 C++ 错误,请确保
- numberplaceview.moc.cpp 已正确编译
- numberplaceview.moc.o 存在
- numberplaceview.moc.o 并且已包含在链接列表中
如果您遇到**任何**错误,请在此处提供反馈,以便我们在此列出并解决它们。
解释
基本上,一切就像您有一个纯 C++ 程序一样。只是,我们创建了一个特殊的新的 cpp 文件,该文件由 qt4
编译器创建,并且需要放入链接列表。这就是构建过程的全部魔力。
重要提示:如果此文件不在列表中,我们将遇到一些奇怪的错误。
另请注意,在我们的构建过程中,我们没有关心 ui_numberplaceview.h。
泛化(实用化)构建命令
我希望您现在已经对构建过程中发生的事情有了一些了解。
现在,没有人愿意在每次开始新项目时都一直像这样列出他们的文件。为此,我们将泛化构建命令,以便 makefile
能自动获取所有必要的文件(通过文件名中的模式)。
注意:提供的 makefile 只在当前目录中搜索源文件。
首先,我们更改结果的路径,使其更易于自定义
OUTPUT_PATH = ./
EXECUTABLE := $(OUTPUT_PATH)executable
我们想要一个中间目录,以免将对象文件弄乱我们的文件夹
TMP_DIR=tmp/
我们需要定义 qt
需要生成的文件。为了更好地理解,我每行只调用一个命令。
我使用模式 *view.h
或 qt*.h
来确定需要生成的文件。这意味着,如果您想使用此自动化,您的需要 qt
编译的文件应该具有此前缀或后缀。您可以随时更改模式或手动添加文件。
# 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))
现在我们准备好指示我们实际需要哪些文件了
# finally: defining all files we need to generate
CPP_FILES_TO_GEN := $(H_FILES_FOR_QT:.h=.moc.cpp)
Make 会自动删除“中间”文件。但我希望保留它们,以便我们可以更好地进行部分重建,因此
# prevent .moc-files being removed by make automatically
.SECONDARY: $(CPP_FILES_TO_GEN)
将所有列表放在一起,创建一个包含所有要编译的 cpp 文件的列表
# recognizing everything, that needs to be compiled
# ---------------------------------------------------
CPP_FILES := $(wildcard *.cpp) $(CPP_FILES_TO_GEN)
对象文件也是如此。但我们希望它们在我们的 tmp 目录中。所以将它们添加到它们的路径中
# defining all object files needed (inside intermediate dir)
# -------------------------------------------------------------
OBJECT_FILES := $(addprefix $(TMP_DIR),$(CPP_FILES:.cpp=.o))
新手提示:@-符号
我使用“@
”符号来抑制命令本身,这样我们就能获得更漂亮的输出。
我们希望确保我们的 TMP_DIR
在对象文件创建在那里之前存在。
# 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)
实际上,对于链接,我们需要 TMP_DIR
,但它在这里不是一个先决条件。原因是,
linkAll: $(OBJECT_FILES)
@echo
@echo ===============================
@echo === compiling finished ========
@echo ===============================
@echo
@echo Linking...
@$(LINK) $? -o $(EXECUTABLE)
这是编译过程的核心。如果一个先决条件“命令”了一个 .o 文件或 moc.cpp,它就会在这里被处理。.o 文件只能在 TMP_DIR 内被命令。moc.cpp 文件在所有源文件所在的位置被命令。
# 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 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)
提及的来源
- 我使用的教程
- 关于 Makefile 中“疯狂”命令(如
$<
)的解释 - 我的树莓派盒子视频(任何对此感兴趣的人,了解它有多“小”和“轻”)
历史
- 2017 年 5 月 30 日:初始版本