从零编译 Qt 源码
写在前头
为了保证流程可复现,本文档展示的编译全过程在 Windows 10 提供的沙盒中进行。
Linux 安装 Qt 想必都不会有什么问题,所以本文讲的其实是 Windows 上如何从零编译 Qt 源码。此处的 Qt 源码指的是 Qt 这个第三库本身,所以想找怎么编译自己写的 Qt 项目的可以到此为止了。
严格来说,跟着 Qt 源码下的 README.md 提供的项目构建流程走是完全没问题的,但是奈何构建过程中多多少少总会出现一些奇奇怪怪的问题,所以才出了这么一篇文档走个还算完整的形式。
获取源码
选择需要编译的 Qt 版本,下载对应的源码包。方便起见,本文直接下载 single 包。
single 包含 Qt 的全部子模块,其中 WebEngine 的大小占 ~75%,如果只是简单使用并且不需要使用浏览器引擎部分的功能的话,可以考虑在 submodules 单独下载 base 模块及其它需要的子模块。
本文以编译 Qt 6.5.0
为例,其它版本类似。
源码下载完成后,解压至任意目录。
下文以 $devel
表示源码解压的目标目录,如图所示,在本文中 $devel
为 ~/Desktop/qt-everywhere-src-6.5.0
。
获取构建工具链
首先的首先,拉完项目先读 README.md
!
Qt 的 README.md 中开门见山地说明了如何构建项目,如 Qt 6.5.0 中就提及了以下构建项目的必需品:
* C++ compiler supporting the C++17 standard
* CMake 3.16 or newer
* Ninja 1.8 or newer
* Python 3
当然还有一个 For more details, see also https://doc.qt.io/qt-6/build-sources.html
。
获取编译工具链
C++ compiler ...
为使用的编译工具链,Windows 上可以使用的主要有 MSVC,MinGW-w64 系列。使用何种编译器在很大程度上会影响最终生成何种 Qt 库。由于几家默认使用的标准库不同,编译过程链接入 Qt 的库也不一致。假定选择使用 LLVM-MinGW 编译,那么 Qt 所使用的便是 libc++。此时当后期混用 MinGW-w64+GCC 编译自己的 Qt 项目并链接该 Qt 库时,若不能保证目标执行程序链接了正确的动态链接库,则极大可能导致程序在进入 main 之前便崩溃退出。
使用何种编译器根据自己的需求而定,但需要保证编译器支持 C++17 及以上标准。
在本文中,使用 LLVM-MinGW with 15.0.0
作为编译工具链。
正确安装并配置环境变量后,在终端中键入相关命令应该可以看到以下类似信息:
$ clang++ --version
clang version 15.0.0 (https://github.com/llvm/llvm-project.git 4ba6a9c9f65bbc8bd06e3652cb20fd4dfc846137)
Target: x86_64-w64-windows-gnu
Thread model: posix
InstalledDir: E:/DevEnvr/usr/local/llvm-mingw-20220906-ucrt-x86_64/bin
获取 CMake
CMake 是一个项目构建工具。用极简且不甚准确的话来说,它是一个允许你以亿点点配置的代价靠一行命令构建所有项目的工具。
好消息是,配置文件是项目维护者写的,我们只需要运行那一行命令就行。坏消息是,写自己的项目想用 CMake 的话……
正确安装并配置环境变量(安装版无需配置)后,在终端中键入相关命令应该可以看到以下类似信息:
$ cmake --version
cmake version 3.25.0
CMake suite maintained and supported by Kitware (kitware.com/cmake).
获取 Ninja
Ninja 同 make 一样是一个构建系统,一个区别在于前者相较于后者具有更精简可读的语法。此处将两者统称为构建系统后端。
构建系统,简而言之就是分析构建目标依赖关系,决定构建命令执行顺序的控制系统。
一个可能的组织是 CMake -> Ninja -> GCC
,在这个例子中,GCC 工具链充当 Ninja 的构建工具后端,Ninja 充当 CMake 的构建系统后端。构建一个由 CMake 管理的项目时,一般的顺序是由 CMake 配置生成 Ninja 配置文件,再由 CMake 驱动 Ninja 进行实际的项目构建,Ninja 则驱动默认或配置的构建工具进行实际的编译工作。
CMake 指定 Ninja 为后端进行项目配置
cmake -B build -S . -G Ninja
从 CMake 根据已配置的后端构建所有目标
cmake --build build
直接使用 Ninja 构建所有目标
ninja -C build
一般而言,构建系统的选择不会影响最终目标的生成。然而 Qt 在项目配置中明确指定 Ninja 作为编译后端,为了避免不必要的麻烦产生,此处考虑听从建议安装 Ninja。
正确安装并配置环境变量后,在终端中键入相关命令应该可以看到以下类似信息:
$ ninja --version
1.11.1
获取 Python 3
对于编程的人来说,Python 想必不是什么稀奇的东西。Python 作为一种便携式的解释型语言,也常参与到项目的配置甚至构建中来。
在 Qt 中,Python 则主要用于相关配置信息的生成。
正确安装并配置环境变量(安装版无需配置)后,在终端中键入相关命令应该可以看到以下类似信息:
$ python --version
Python 3.11.0
项目配置
从该步骤开始,正式进行 Qt 源码的编译。在开始前,首先进入终端切入 Qt 源码的根目录
cd $devel
注意前文,此处 $devel 指源码解压的目标目录
configure.bat
是 Qt 用于 Windows 下的项目配置脚本。由于 README.md 中提供的信息有限,所以可以依靠以下命令获取具体的项目构建配置提示
configure -help
根据需求阅读 help 信息,下面会给出一些常用的配置选项
部署路径
-prefix <dir> ... 指定构建完成后 Qt 的安装路径,默认为 `/usr/local/Qt-$QT_VERSION`
构建版本
-release ... Release 版本(默认)
-debug ... Debug 版本
-debug-and-release ... Release & Debug 双版本(Windows 默认)
-optimize-debug ... 启用 Debug 版本的优化
-optimize-size ... 启用 Release 版本的优化
-force-debug-info ... 强制创建 Release 版本的调试信息
链接库类型
-shared ... 生成动态链接库(默认)
-static ... 生成静态链接库
构建目标
-submodules <repo>[,<repo>] ... 指定需要构建的子模块(自动包含模块的依赖项)
-skip <repo>[,<repo>] ... 指定不参与构建的子模块
-gui ... 构建 Qt GUI 模块及依赖项
-widgets ... 构建 Qt Widgets 模块及依赖项
-make <part> ... 指定需要参与构建的部分,包括 libs, tools, examples, tests, benchmarks, manual-tests, minimal-static-tests,默认为 libs, examples
-nomake <part> ... 指定不参与构建的部分,可选值同上
配置样例
指定构建的模块
- Qt Widgets
- Qt GUI
指定构建的部分
- libs
- examples
- tools
指定构建的库类型
- 动态链接库
指定构建的版本
- Release
- Debug
指定 CMake 的构建目录
$devel/build
指定部署路径
~/Desktop/Qt-6
最终的配置命令
configure ^
-prefix ~/Desktop/Qt-6 ^
-shared ^
-widgets -gui ^
-make tools ^
-- -B build
configure 可以理解为是使用 CMake 进行项目配置的封装。即使不使用 configure.bat 脚本,也可以直接通过 CMake 进行项目的配置。只不过后者可能需要构建者对项目的配置细节了解得更深,否则很容易出现配置选项的遗漏、冲突、无效等错误。
项目构建
在配置样例中,指定了 $devel/build
为 CMake 的构建目录,在 $devel
下执行下列命令使用 CMake 进行项目的构建
cmake --build build
不出意外的话,除了报了一堆 WARNING 是没有错误的。
构建成功后继续使用 CMake 部署 Qt,使用以下命令
cmake --install build
或
cmake --build build --target install
构建完成的内容会被安装到配置时指定的部署路径
到这里为止,已经完成了所需的 Qt 模块的编译与安装。Qt 库的导入支持通过 CMake 配置,在简单使用中,会介绍如何使用 CMake 导入 Qt 库并将使用已经安装的 Qt 模块。
简单使用
为了能够让 CMake 知道 Qt 位于何处,需要配置环境变量 CMAKE_PREFIX_PATH
,该变量所指定的路径将被用于 CMake 的外部包搜索。
对于 CMAKE_PREFIX_PATH
,CMake 将从其路径所指定的文件夹开始向子目录递归地搜索 cmake
目录,该目录下包含了 CMake 的配置脚本。对于 Qt 来说,可以直接将部署目录加入 CMAKE_PREFIX_PATH
变量。
完成上述步骤后,简单编写一个 Qt Hello World 程序,此处将其命名为 foobar.cpp
#include <QApplication>
#include <QWidget>
#include <QLabel>
#include <QVBoxLayout>
#include <memory>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
auto w = std::make_unique<QWidget>();
auto label = new QLabel("Hello World!");
label->setAlignment(Qt::AlignCenter);
auto layout = new QVBoxLayout(w.get());
layout->addWidget(label);
w->setFixedSize(QSize(320, 100));
w->show();
return app.exec();
}
编写 CMake 的配置脚本 CMakeLists.txt 如下
# 约束使用的 CMake 版本
cmake_minimum_required(VERSION 3.21)
# 声明项目
project(foobar)
# 从提供的名称中寻找 Qt 库,此处限制使用 Qt6
find_package(Qt NAMES Qt6)
# 寻找 Qt6 的 Widgets 模块,REQUIRED 限制寻找失败则 FATAL
find_package(Qt6 REQUIRED COMPONENTS Widgets)
# 声明可执行文件目标,参与的源文件为 foobar.cpp
# WIN32 指明构建窗口程序(不会弹出控制台)
add_executable(foobar WIN32 foobar.cpp)
# 为可执行目标链接 Qt 模块
target_link_libraries(
# ${CMAKE_PROJECT_NAME} 为根 CMakeLists.txt 声明的项目名
# 对于当前层,使用 ${PROJECT_NAME} 指定
# 此处 ${PROJECT_NAME} 即为 ${CMAKE_PROJECT_NAME}
${CMAKE_PROJECT_NAME}
# 链接 Qt 的 Widgets 模块
PRIVATE Qt6::Widgets
)
其中 CMakeLists.txt 与 foobar.cpp 位于同目录(记为 $src
)。
使用 CMake 在 $src/build
下构建:
cd $src
cmake -B build -S .
cmake --build build
目标可执行文件将生成在 build 目录下,直接运行或在终端 build\foobar.exe
运行,可以看到窗口正常弹出
由于构建的 Qt 为动态链接库的版本,故运行目标 Qt 项目需要加载 dll。
在本机上,可以简单地将 Qt 部署路径的 bin 目录(记为 $bin)添加到环境变量中。
在发行或计划在其它电脑上运行时,可以使用 Qt 提供的 $bin/windeployqt 对目标 Qt 可执行文件(如上述的 foobar.exe)进行部署。
默认参数下,windeployqt 会将相关依赖打包到目标文件的同目录下。