【配置分享】CMake构建Qt



  • 原文参考: https://blog.csdn.net/qq_32768743/article/details/80056316

    【配置分享】CMake构建Qt

    写在前面的话:强烈建议优先阅读Qt官网文档对cmake的使用介绍——CMake Manual

    前言

    我去年用clion写Qt的时候,找了很多教程,也没有什么让我觉得很满意的。后来自己摸索,构建了一个我自己比较喜欢的方式。我的环境是Deepin 15.5。

    在Deepin上,获取Qt环境最快的方式莫过于sudo apt install qtcreator-dde。它会帮你安装qt开发包,qt集成环境等。由于我自己非常喜欢JetBrains家的产品,如Intellij IDEA等,当它出了c++的IDE时,也非常希望能用JetBrains家的产品做Qt开发。我目前的环境是Clion 2018.1。

    一个简单的案例

    首先介绍一下创建一个Qt的工程,这和创建一个普通的C++工程是一样的。

    打开CLion

    创建新项目QtDemo

    创建好了以后,Building Symbols会花一些时间。

    运行一下Demo代码。

    运行效果

    接下来,我们进行Qt工程的改造。

    目录结构

    pikachu@pikachu-PC:~/src/Demo/QtDemo$ tree
    .
    ├── CMakeLists.txt
    └── src
        ├── CMakeLists.txt
        └── main.cpp
    
    1 directory, 3 files
    pikachu@pikachu-PC:~/src/Demo/QtDemo$ 
    
    

    把模板代码贴上

    CMakeLists.txt

    cmake_minimum_required(VERSION 3.10)
    project(QtDemo)
    add_subdirectory(src)
    

    src/CMakeLists.txt

    cmake_minimum_required(VERSION 3.7)
    set(TARGET_NAME QtDemo)
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    set(CMAKE_AUTOUIC ON)
    file(GLOB_RECURSE SOURCES "*.cpp")
    file(GLOB_RECURSE HEADERS "*.h")
    file(GLOB_RECURSE FORMS "*.ui")
    file(GLOB_RECURSE RESOURCES "*.qrc")
    find_package(PkgConfig REQUIRED)
    set(QT Core Gui Widgets Network DBus Sql)
    find_package(Qt5 REQUIRED ${QT})
    pkg_check_modules(3rd_lib REQUIRED
            dtkwidget dframeworkdbus
            )
    add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS} ${FORMS} ${RESOURCES})
    target_include_directories(${TARGET_NAME} PUBLIC ${3rd_lib_INCLUDE_DIRS} )
    target_link_libraries(${TARGET_NAME} ${3rd_lib_LIBRARIES} )
    qt5_use_modules(${TARGET_NAME} ${QT})
    set(CMAKE_INSTALL_PREFIX /usr)
    install(TARGETS ${TARGET_NAME} DESTINATION bin)
    
    

    使用了DTK的模板代码

    #include <DApplication>
    #include <DUtil>
    #include <DMainWindow>
    #include <DWidgetUtil>
    #include <DLog>
    #include <zconf.h>
    
    DWIDGET_USE_NAMESPACE
            DCORE_USE_NAMESPACE
    int main(int argc, char *argv[]) {
        DLogManager::registerConsoleAppender();
        DLogManager::registerFileAppender();
        DApplication::loadDXcbPlugin();
        DApplication app(argc, argv);
        app.setAttribute(Qt::AA_UseHighDpiPixmaps);
        const QString socket_path(QString("QtDemo_%1").arg(getuid()));
        if (app.setSingleInstance(socket_path)) {
            app.setTheme("light");
            app.loadTranslator();
            const QString descriptionText = QApplication::tr("A simple Qt Demo by CLion 2018.1 and CMake");
            const QString acknowledgementLink = "https://github.com/PikachuHy";
            app.setOrganizationName("pikachu");
            app.setApplicationName("QtDemo");
            app.setApplicationDisplayName(QObject::tr("Qt Demo"));
            app.setApplicationVersion("1.0.0");
            app.setProductName(QApplication::tr("Qt Demo"));
            app.setApplicationDescription(descriptionText);
            app.setApplicationAcknowledgementPage(acknowledgementLink);
    
            DMainWindow window;
            window.setFixedWidth(600);
            Dtk::Widget::moveToCenter(&window);
            window.show();
            return app.exec();
        }
        qDebug() << "app has started";
        return 0;
    }
    
    

    运行效果

    运行效果

    分析与解释

    接下来,介绍一些这样做的优势和原因

    首先是目录结构,我是在根目录下又套了一个src目录,为什么这样做呢?

    自然,这是有使用Qt的原因在的。Qt自己有一个moc编译器,会自动生成一些代码。下面的代码就是让Qt自动生成相关的代码,无需我们自己干预。

    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    set(CMAKE_AUTOUIC ON)
    

    另一个是CLion本身的原因。CLion会为每个项目创建如cmake-build-debug这样的配置文件,而我常常是使用后缀名来匹配文件,如果不隔离,会导致函数重定义的问题。建立一个src目录可以很好的解决这个问题。搜索文件的代码如下

    file(GLOB_RECURSE SOURCES "*.cpp")
    file(GLOB_RECURSE HEADERS "*.h")
    file(GLOB_RECURSE FORMS "*.ui")
    file(GLOB_RECURSE RESOURCES "*.qrc")
    

    通常,我们会用到Qt的很多模块,在cmake中怎么体现呢?我在寻找了很久后,发现了一个非常简洁的写法。你只需要在set(QT ...)这里添加你需要的模块即可。代码如下:

    set(QT Core Gui Widgets Network DBus Sql)
    find_package(Qt5 REQUIRED ${QT})
    qt5_use_modules(${TARGET_NAME} ${QT})
    

    嗯,看样子还是很好的。可是,使用C++,经常要用到的是它的生态,如何使用第三方库呢?在这里,我是这样定义的,先看代码:

    find_package(PkgConfig REQUIRED)
    pkg_check_modules(3rd_lib REQUIRED
            dtkwidget dframeworkdbus
            )
    target_include_directories(${TARGET_NAME} PUBLIC ${3rd_lib_INCLUDE_DIRS} )
    target_link_libraries(${TARGET_NAME} ${3rd_lib_LIBRARIES} )
    

    我依赖了2个第三方的包,dtkwidgetdframeworkdbus,以及定义一个3rd_lib的变量,然后通过target_include_directoriestarget_link_libraries用在项目中。如果需要添加新的库,也仅仅是在pkg_check_modules(3rd_lib REQUIRED ...)中,写上你库的名字,非常方便。

    如果你对QtCreator的配置文件.pro文件很熟悉的话,你会很惊讶的发现,我用CMake的写法和用pro文件的写法有诸多类似的地方。为什么呢?因为我常常需要在QtCreator和CLion两个IDE之间切换,以便我更好的利用两个IDE的优势,(QtCreator对Qt良好的支持,CLion对重构的良好支持)。

    对CMakeLists.txt的介绍就到这里了,如果你对它的代码还有什么疑问,可以参考附录中的注释。

    附:

    CMakeLists.txt

    # 需用使用的最小的CMake版本
    cmake_minimum_required(VERSION 3.7)
    # 本次构建的可执行文件名称
    set(TARGET_NAME QtDemo)
    # 本次使用C++标准版本
    set(CMAKE_CXX_STANDARD 17)
    # 字面意思,包含当前目录,可以方便开发
    set(CMAKE_INCLUDE_CURRENT_DIR ON)
    # 开启Qt代码自动生成,不再需用自己手写了。按顺序,分别是`Q_OBJECT`宏展开,资源文件,界面文件。
    set(CMAKE_AUTOMOC ON)
    set(CMAKE_AUTORCC ON)
    set(CMAKE_AUTOUIC ON)
    # 简单粗暴的把源码搜集起来。按顺序,分别是实现文件,头文件,界面文件,资源文件
    file(GLOB_RECURSE SOURCES "*.cpp")
    file(GLOB_RECURSE HEADERS "*.h")
    file(GLOB_RECURSE FORMS "*.ui")
    file(GLOB_RECURSE RESOURCES "*.qrc")
    # 使用第三方库需要用到的一个包
    find_package(PkgConfig REQUIRED)
    # 使用Qt的模块,写法和.pro文件类似
    set(QT Core Gui Widgets Network DBus Sql)
    find_package(Qt5 REQUIRED ${QT})
    # 使用的第三方模块
    pkg_check_modules(3rd_lib REQUIRED
            dtkwidget dframeworkdbus
            )
    add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS} ${FORMS} ${RESOURCES})
    target_include_directories(${TARGET_NAME} PUBLIC ${3rd_lib_INCLUDE_DIRS} )
    target_link_libraries(${TARGET_NAME} ${3rd_lib_LIBRARIES} )
    # 在CMake中使用Qt最快捷的方式,一句代码搞定
    qt5_use_modules(${TARGET_NAME} ${QT})
    # 字面意思,安装文件的前缀
    set(CMAKE_INSTALL_PREFIX /usr)
    # 安装可执行文件
    install(TARGETS ${TARGET_NAME} DESTINATION bin)
    


  • 这个是我写ss-client的摸索出来的一套,我觉得挺方便的。尤其是同时需要用.pro和cmake时。



  • @大黄老鼠 Qt能够支持其它的IDE有一点不能忽视就是Qt增强了对cmake的支持。目前Qt支持的构建系统为cmake、qmake和qbs。



  • @jiangcaiyang 以前用windows的时候,基本没有成功使用过CMake管理Qt.linux就方便多了。
    qbs好像是写法和qml差不多的一个管理工具。
    印象中除了QtCreator对CMake的支持一直在加强以外,Qt本身没有对CMake增加什么支持吧?



  • @大黄老鼠 Qt产品当然支持cmake啦。cmake-gui就是Qt制作的,以至于cmake都有给Qt预留的一套脚本。然后Qt Creator对cmake的支持也在增强,虽然我现在连一个qtwebkit都没有编译出来……
    0_1524497133126_2222.jpg



  • @大黄老鼠【配置分享】CMake构建Qt 中说:

    clion很像pycharm



  • @青山白云
    他们都有同一个父亲,叫做Intellij IDEA。一个写Java的。



  • @大黄老鼠 这家公司是捷克的JetBrain团队。不过反过来会被当作典型案例推广Java跨平台的优势。


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more

关注我们

微博
QQ群