使用cmake构建Qt6软件
使用cmake构建Qt程序的时候,和构建其他程序有所不同,这里记录一下
Qt官方逐渐的放弃了qmake构建体系,切换到了cmake。我做了一些尝试把过去的qmake体系下的一些软件迁移到了cmake体系,这里记录一下,以备不时之需。
cmake的一般用法和其他的软件没有什么区别,比如说cmake_minimum_required、project、set这些常用命令。但是涉及到Qt会有一些额外的命令。
自动预处理
Qt有3个重要的预处理命令:moc、uic、rcc
moc用于处理包含Q_OBJECT宏的类,uic用于处理QtDesigner设计的ui,rcc用于处理资源文件。
为了保证预处理命令的运行,在CMakeLists.txt中要加入一些内容
对于单一编译目标的项目,在CMakeListx.txt中加入
# auto MOC, UIC, RCC
set_target_properties(${PROJECT_NAME} PROPERTIES
AUTOMOC ON
AUTOUIC ON
AUTORCC ON
)
对于多编译目标的项目,可以在每个需要单独处理的submodule的CMakeLists.txt中加入
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ui)
cmake编译Qt程序的基本的设定
二进制目标
一般的程序,cmake会这样设置二进制目标
cmake_minimum_required(VERSION 3.14)
set(APP_NAME myName)
project(${APP_NAME})
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
add_executable(${APP_NAME } ${SRC_LIST})
对于Qt程序,则要稍作调整
cmake_minimum_required(VERSION 3.14)
set(APP_NAME myName)
project(${APP_NAME})
qt_standard_project_setup()
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
qt_add_executable(${APP_NAME} ${SRC_LIST})
qt_add_executable指令会增加一些Qt专属的设置。
要特别提醒的是cmake自带的add_executable命令会把第一个参数设定为APP_NAME变量,而qt_add_executable指令不会,所以最好是搭配set命令单独设置这个变量
链接库
Qt专用的链接库
target_link_libraries(${APP_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
这是最常用的Qt组件库,如果需要其他的组件库,追加到后面就好
部署
如果在windows平台采用vcpkg作为依赖包的管理器,那么将会遇到一些插件的问题。
Qt默认自带的插件不是安装在bin目录的,在vcpkg中Qt的插件安装在Qt6/plugins目录下。
所以,当编译完成后,运行可执行程序的时候,会提示找不到插件。
把需要的插件复制到可执行程序的对应目录就可以了。但是手工处理的话很难搞清楚。
https://github.com/microsoft/vcpkg/issues/43668
这里的讨论提示在CMakeLists.txt中增加一条指令
add_custom_command(TARGET ${APP_NAME} POST_BUILD COMMAND Qt6::windeployqt --qmldir "${CMAKE_CURRENT_SOURCE_DIR}/qml" "$<TARGET_FILE:${APP_NAME}>" )
就可以自动把依赖的二进制文件复制到合适的目标目录
前面添加的qt_add_executable指令会自动处理插件的依赖关系,然后这步通过windeployqt来把需要的二进制文件复制到合适的位置。
最后清理
qt_finalize_project | Qt Core 6.8.2
在整个CMakeLists.txt文件的最后还要增加一条指令
qt_finalize_project()
这样就可以保证cmake在构建Qt程序的时候能够把需要处理的都处理掉。
按照Qt官方的说法,这个指令将来可能会变,到时候再说。
小结
总结起来CMakeLists.txt就像这样
cmake_minimum_required(VERSION 3.14)
set(APP_NAME myName)
project(${APP_NAME})
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ui)
qt_standard_project_setup()
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
qt_add_executable(${APP_NAME} ${SRC_LIST})
target_link_libraries(${APP_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
add_custom_command(TARGET ${APP_NAME} POST_BUILD COMMAND Qt6::windeployqt --qmldir "${CMAKE_CURRENT_SOURCE_DIR}/qml" "$<TARGET_FILE:${APP_NAME}>" )
qt_finalize_project()
一个简单的Qt程序的CMakeListx.txt就是这样,从qt_standard_project_setup()开始,到qt_finalize_project()结束。
但是实际开发中往往会遇到更多的需求,这里记录一下我遇到的需求在cmake构建系统中的处理方法。
去掉控制台
Qt主要用于开发图形界面程序,但是如果代码中含有一些控制台的函数,比如qDebug(),那么,Qt程序编译的时候就会带着控制台的相关代码。在运行的时候,除了主图形界面,还会附带一个控制台,并在控制台输出相关内容,有些甚至不是开发者自己写的代码,而是Qt自带的一些代码。
https://forum.qt.io/topic/121502/cmake-shows-console/3
CMakeLists.txt中加入这条指令,编译出来的Qt程序在运行的时候就不带控制台了
# hide the console
set_property(TARGET ${APP_NAME} PROPERTY WIN32_EXECUTABLE true)
多国语言
不需要多国语言的Qt软件CMakeLists.txt是这样设置的
qt_standard_project_setup()
如果需要多国语言,那么就需要调整一下设置,这样在编译后的软件中zh_CN的翻译部分会被嵌入到最终生成的二进制程序中。
qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES zh_CN)
qt_add_translations | Qt Linguist Manual
对于Qt多国语言程序,主要是在代码中涉及到多国语言的位置使用QString.tr()函数。Qt提供了一个linguist语言家程序可以扫描代码中的tr()函数,并汇总为一个扩展名为ts的文件,实质上这是一个xml格式的文件。Qt语言家可以方便的编辑这个ts文件,开发者可以把这里面的词句翻译为目标语言,当然如果不用Qt语言家来编辑,而是采用其他文本编辑器也是可以的。
在cmake构建系统内,Qt提供了自动调用linguits的功能,不过需要CMakeLists.txt中加入
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets LinguistTools)
注意最后的LinguistTools,带上这一项才能保证cmake能调用linguist,按照开发者要求在构建过程中执行相应的指令。linguist是QtTools的子组件。
在cmake构建系统里,linguist提供了一些指令
CMake Commands in Qt6 LinguistTools | Qt Linguist Manual
lrelease命令是扫描所有源代码文件,找出里面的tr()函数,并汇总到一个ts文件里。当然,它不会自动翻译里面的内容,开发者需要自己翻译。
lupdate命令是在源代码文件有所变更的情况下,扫描源代码文件,并把变更更新到对应的ts文件里。如果是全新的ts文件,直接用lrelease重新生成就好了;如果是已经翻译好的文件,不能丢弃已经翻译好的部分,就要用到lupdate
add_translation计划要废弃
add_translations是把xml格式的ts文件编译成二进制的qm文件,这个qm文件可以嵌入到可执行程序里,将来这个可执行程序就可以显示对应的语言。在add_translations里,create_translation会被自动调用,我不用create_translation。
在CMakeLists.txt里添加
qt_add_translations(${APP_NAME}
TS_FILE_BASE basename
TS_FILE_DIR translation
RESOURCE_PREFIX "/lang"
)
APP_NAME就是前面qt_add_executable生成的变量
TS_FILE_BASE是涉及到所有ts文件命名的参数。所有的ts文件应该按照<TS_FILE_BASE>_<LANG>.ts来命名。如果这个选项留空,那就会被自动设定为${PROJECT_NAME}
TS_FILE_DIR选项是指从CMakeLists.txt所在的目录出发,找到ts文件的相对路径。
RESOURCE_PREFIX是在源代码的QTranslator中调用时,需要设定的部分。比如说这里设定的RESOURCE_PREFIX是"/lang",那么在源代码中
QApplication app(argc, argv);
QTranslator translator;
const QStringList uiLanguages = QLocale::system().uiLanguages();
for (const QString &locale : uiLanguages)
{
const QString translationFileName = "basename_" + QLocale(locale).name(); // 6
if (translator.load(":/lang/" + translationFileName))
{
app.installTranslator(&translator);
break;
}
}
第6行的basename就是前面设定的TS_FILE_BASE
QLocale(locale).name()就是语言的代码,比如说zh_CN或者en_US
它们中间加一个下划线_组合在一起就是translationFileName
而在translator.load函数中":/lang/" + translationFileName会组合成一个字符串,这个字符串以冒号:开头表明这是一个资源,后面用/分段。第一段的/lang就是RESOURCE_PREFIX,第二段就是TS_FILE_BASE和语言代码组成的translationFileName。
意思就是说通过:判断出这是一个资源。因为CMakeLists.txt中qt_add_translations设置了lang是翻译资源的意思,在构建时源代码中遇到lang就识别为翻译资源。构建时在qt_add_translations里设置的TS_FILE_DIR目录下找到<TS_FILE_BASE>_<LANG>.ts也就是源代码中的translocationFileName找到这个翻译文件。交给qrc和编译器做最后的处理。
qt_add_translations的更多选项可以参考
qt_add_translations | Qt Linguist Manual
qt_add_lrelease和qt_add_lupdate虽然可以在cmake构建系统中自动执行,但是我觉得意义不大,因为就算生成了ts文件,还是要人来手动翻译才能完成,所以这步应该在编码翻译阶段用,而不是等到自动构建阶段再用。
linguist和QtCreator集成非常好,QtCreator是GPL和商业许可的软件,商用的话需要付费,linguist命令行工具是免费的。如果想用cmake调用linguist命令行来工作也不是不可以,这样可以实现一些自动批处理,可以调整一下cmake的target。这样的话,直接用命令行也可以。
资源的处理
cmake处理Qt资源有2种方法,一种是采用qrc定义资源文件的方法,另一种是在CMakeLists.txt中使用qt_add_resources指令的方法。
qrc定义资源文件
The Qt Resource System | Qt Core 6.8.2
QtCreator可以创建qrc资源文件并很方便的编辑,如果打开qrc文件就会发现,其实它是一个纯文本的文件,xml格式的。内部大概是这样
<RCC>
<qresource prefix="/icon">
<file>images/copy.png</file>
<file alias="cut">images/cut.jpg</file>
<file>images/new.png</file>
<file>images/open.png</file>
<file>images/paste.png</file>
<file>images/save.png</file>
</qresource>
<qresource prefix="/icon" lang="fr">
<file alias="images/cut.jpg">images/cut_fr.jpg</file>
</qresource>
</RCC>
当Qt使用资源文件的时候,可以直接使用绝对路径,比如说源代码里
copy_icon = new QIcon("C:/project/icons/copy.png");
这种方法的缺点是显而易见的,一旦项目目录移动,绝对路径就失效了。如果采用以.和..的相对路径,也有类似的问题,因为不同的构建系统输出的应用程序的目录是不一致的。
如果采用资源文件的方法就可以避免类似问题。上面的源代码可以改为
copy_icon = new QIcon(":/icons/images/copy.png");
路径以冒号:开头代表这是一个资源路径,后面以/分割为几段。
第一段icons就是前缀,它和qrc文件中的prefix一致。实际上在qrc文件中,可以在不同的块设置不同的prefix,这样就可以把不同的资源分开。
第二段images/copy.png就是资源文件中的具体条目的内容。如果具体的条目设置有别名alias,那么在源代码中调用资源时就要用别名,比如说
cut_icon = new QIcon(":/icons/cut");
这里的cut就是images/cut.jpg的别名
同样的资源还可以设定不同的语言,当程序在不同语言运行时,会根据语言切换资源。比如说当程序运行在法语环境时,cut.jpg图标会自动切换为cut_fr.jpg的图像,但是开发者在编码中要把它alias到那个没有语言设定的资源的本名(不是别名),也就是images/cut.jpg
也就是说,在Qt的资源系统里,如果遇到了冒号:开头的就会识别为资源,然后紧跟着分析是否匹配prefix,匹配好以后根据别名alias去找对应的资源,如果没有别名就会去直接找相应的资源,资源放在哪里则是在qrc文件中定义好的,别名和实际资源名的对应关系是一一对应的。
在cmake构建系统中处理qrc资源文件的时候,要这样
set(CMAKE_AUTORCC ON) # 前面已经说过了,启用自动rcc
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
qt_add_executable(${APP_NAME} WIN32
resource.qrc
${SRC_LIST}
)
先启动自动rcc,然后在qt_add_executable指令中,把qrc资源文件和源代码一起加入进去。在构建的时候,cmake就会自动调用rcc处理资源文件,然后在编译源代码的时候自动把资源加入进去。
在CMakeLists.txt中使用qt_add_resources指令
qt_target_qml_sources | Qt Qml 6.8.2
在CMakeLists.txt文件中,这样添加指令
set(CMAKE_AUTORCC ON) # 前面已经说过了,启用自动rcc
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
qt_add_executable(${APP_NAME} WIN32 ${SRC_LIST})
qt_add_resources(${APP_NAME} icons
PREFIX "/icons"
FILES images/copy.png images/cut.jpg images/new.png images/open.png images/paste.png images/save.png
)
set_source_files_properties(images/cut.jpg PROPERTIES
QT_RESOURCE_ALIAS cut
)
qt_add_resources(${APP_NAME} icons_fr
PREFIX "/icons"
LANG fr
FILES images/cut-fr.jpg
)
set_source_files_properties(images/cut-fr.jpg PROPERTIES
QT_RESOURCE_ALIAS images/cut.jpg
)
qt_add_resources指令的前面两个参数分别是目标和资源名,如果要多次添加,那么资源名必须要不同。
后面的PREFIX和qrc中的prefix是一个意思,在调用的时候要紧跟在冒号后面。
LANG参数就是语言
FILES就是资源的文件名,这里写的是从CMakeLists.txt出发的相对路径名
但是qt_add_resources指令不能设定别名,虽然在一般资源的使用中可以不使用别名,而使用原始文件名,但是在涉及到语言选择时,别名是必须要有的。这个时候要set_source_files_properties命令来处理一下,如果需要处理的文件比较多的话,还是挺烦的
在源码使用资源时用法和qrc方案的用法完全一样
cut_icon = new QIcon(":/icons/cut");
以冒号:开头表明这是一个资源,然后用/分割几段。第一段是PREFIX,也就是icons;后面是别名
这个别名对应的images/cut.jpg在法语中还有一个替代品images/cut-fr.jpg,所以在语言切换到法语的时候,这个图标会自动切换到cut-fr.jpg
对比qrc方法和qt_add_resources方法,我还是更倾向于使用qrc的方案来管理资源
更多cmake相关设置可以参看
CMake Commands in Qt6 Core | Qt Core 6.8.2
软件自身图标
Setting the Application Icon | Qt 6.8
软件内部的图标按照一般资源来处理就可以了,但是在windows系统下,软件本身还有一个图标,这个图标不仅会显示在windows文件管理器里,在软件运行时还会显示在任务栏。这个图标没法按照一般资源的方式来处理。如果是命令行程序或者后台程序,没有图形界面,这个图标也就无所谓了。但是Qt一般用于开发GUI程序,所以图标是很重要的。
首先要创建一个rc文件,比如说title.rc文件。这是Visual Studio开发时使用的资源文件格式,可以通过Visual Studio创建。里面包含3列,前两列是固定格式,第3列是图标文件,是从CMakeLists.txt出发的相对路径
IDI_ICON1 ICON "icons/myappico.ico"
然后把这个rc文件加入到qt_add_executable指令里。
set(CMAKE_AUTORCC ON) # 前面已经说过了,启用自动rcc
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
qt_add_executable(${APP_NAME} WIN32
resource.qrc
${SRC_LIST}
title.rc # 新增的这行就是软件本身的图标
)
编译出来的可执行程序就带了图标
Linux系统不需要这么处理,因为Linux系统自身不带图形界面,图形界面中涉及到图标的时候都是通过另外定义.desktop文件的,跟程序本身是否带图标无关。
qwt插件
Qwt User's Guide: Qwt - Qt Widgets for Technical Applications
qwt是Qt的绘图插件,功能十分强大,不过已经多年没有更新。当年qwt是qmake构建体系开发的,现在Qt整体切换到了cmake构建体系,所以qwt在cmake下使用有点困难。
vcpkg项目把qwt集成了进来,并且提供了一个cmake模块,感谢微软的贡献
https://githubissues.com/microsoft/vcpkg/35522
只要在CMakeLists.txt中添加
find_package(unofficial-qwt CONFIG REQUIRED)
target_link_libraries(${APP_NAME} PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets unofficial::qwt::qwt) # 如果有其他的库需要链接也要加上
注意最后的unofficial::qwt::qwt
这样在使用cmake构建的时候就可以自动处理qwt插件了
更多推荐
所有评论(0)