Qt开发的程序的自动部署

不管是在Linux环境下,还是Windows环境下,使用Qt开发的程序,都需要依赖Qt的动态连接库才能运行。

所以,为了使用我们程序的用户的便利,我们给用Qt开发的程序做安装包的时候,把这些依赖的动态链接库都一起打包,会给我们的用户极大的方便。

但是,一个程序的运行需要什么动态库,一个一个地手动找出来,实在是太累了。做为程序员,我们都会通过自动化的工具,来实现这个操作。

通用思路

首先最通用的方法是,我们可以把编译出来的程序,使用二进制分析工具,进行分析,看它都依赖了哪些动态链接库。

在不同的操作系统下,有不同的实现。

GNU/Linux环境

对于Linux下的程序,即elf文件,可以使用ldd来命令,来解析出来程序需要的.so文件。

比如,我们可以通过如下命令,来获取/usr/bin/konsole程序的依赖:

 ldd /usr/bin/konsole

执行以后,将得到如下输出:

       linux-vdso.so.1 (0x00007f501757d000)  
       libkonsoleapp.so.24.05.0 => /lib64/libkonsoleapp.so.24.05.0 (0x00007f50174e9000)  
       libkonsoleprivate.so.24.05.0 => /lib64/libkonsoleprivate.so.24.05.0 (0x00007f5017200000)  
       libKF6DBusAddons.so.6 => /lib64/libKF6DBusAddons.so.6 (0x00007f50174c8000)  

……

       libunistring.so.5 => /lib64/libunistring.so.5 (0x00007f500dca0000)  
       libevent-2.1.so.7 => /lib64/libevent-2.1.so.7 (0x00007f500dc49000)  
       libsasl2.so.3 => /lib64/libsasl2.so.3 (0x00007f500dc2b000)  
       libcrypt.so.2 => /lib64/libcrypt.so.2 (0x00007f500dbf1000)

为了方便,我们可以写一个简单的shell脚本来自动化完成这个操作。

如:

ldd /usr/bin/konsole | awk -F=\> '{print $2}' | awk '{print $1}' | xargs

就可以把程序的依赖列表整理成一个复制文件的列表。

Windows环境

在Windows环境下,如果是Cygwin等类Linux环境,仍然可以使用ldd命令。

但如果没有ldd命令,可以使用dumpbin命令。这个命令的/dependents参数,可以输出二进制可执行程序依赖的库文件。

如:

dumpbin /dependents c:\windows\system32\regedt32.exe

就可以得到如下的输出:

Microsoft (R) COFF/PE Dumper Version 14.40.33811.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file c:\Windows\System32\regedt32.exe

File Type: EXECUTABLE IMAGE

  Image has the following dependencies:

    KERNEL32.dll
    msvcrt.dll
    SHELL32.dll
    api-ms-win-core-shlwapi-legacy-l1-1-0.dll

  Summary
……

Qt自带的工具打包

除了以上这种查看二进制依赖的通用方法,Qt的开发工具之中,也提供了部署Qt程序的命令,可以用来自动化地安装部署程序的依赖项。

Qt开发库在Windows下的相应工具名为windeployqt。

我们可以在编译好程序以后,使用windeploqt自动地把程序的所有依赖自动部署。而且,这个命令不仅会部署动态链接库,程序依赖到的其它文件、插件等,也会按照正确的目录,自动进行部署。

执行windeployqt --help可以查看windeployqt支持的参数:

The simplest way to use windeployqt is to add the bin directory of your Qt
installation (e.g. <QT_DIR\bin>) to the PATH variable and then run:
  windeployqt <path-to-app-binary>

If your application uses Qt Quick, run:
  windeployqt --qmldir <path-to-app-qml-files> <path-to-app-binary>

Options:
  -?, -h, --help                         Displays help on commandline options.
  --help-all                             Displays help, including generic Qt
                                         options.
  -v, --version                          Displays version information.
  --dir <directory>                      Use directory instead of binary
                                         directory.
  --qmake <path>                         Use specified qmake instead of qmake
                                         from PATH. Deprecated, use qtpaths
                                         instead.
  --qtpaths <path>                       Use specified qtpaths.exe instead of
                                         qtpaths.exe from PATH.
  --libdir <path>                        Copy libraries to path.
  --plugindir <path>                     Copy plugins to path.
  --translationdir <path>                Copy translations to path.
  --qml-deploy-dir <path>                Copy qml files to path.
  --debug                                Assume debug binaries.
  --release                              Assume release binaries.
  --pdb                                  Deploy .pdb files (MSVC).
  --force                                Force updating files.
  --dry-run                              Simulation mode. Behave normally, but
                                         do not copy/update any files.
  --no-patchqt                           Do not patch the Qt6Core library.
  --ignore-library-errors                Ignore errors when libraries cannot be
                                         found.
  --no-plugins                           Skip plugin deployment.
  --include-soft-plugins                 Include in the deployment all relevant
                                         plugins by taking into account all soft
                                         dependencies.
  --skip-plugin-types <plugin types>     A comma-separated list of plugin types
                                         that are not deployed
                                         (qmltooling,generic).
  --add-plugin-types <plugin types>      A comma-separated list of plugin types
                                         that will be added to deployment
                                         (imageformats,iconengines)
  --include-plugins <plugins>            A comma-separated list of individual
                                         plugins that will be added to
                                         deployment (scene2d,qjpeg)
  --exclude-plugins <plugins>            A comma-separated list of individual
                                         plugins that will not be deployed
                                         (qsvg,qpdf)
  --no-libraries                         Skip library deployment.
  --qmldir <directory>                   Scan for QML-imports starting from
                                         directory.
  --qmlimport <directory>                Add the given path to the QML module
                                         search locations.
  --no-quick-import                      Skip deployment of Qt Quick imports.
  --translations <languages>             A comma-separated list of languages to
                                         deploy (de,fi).
  --no-translations                      Skip deployment of translations.
  --no-system-d3d-compiler               Skip deployment of the system D3D
                                         compiler.
  --no-system-dxc-compiler               Skip deployment of the system DXC
                                         (dxcompiler.dll, dxil.dll).
  --compiler-runtime                     Deploy compiler runtime (Desktop
                                         only).
  --no-compiler-runtime                  Do not deploy compiler runtime
                                         (Desktop only).
  --json                                 Print to stdout in JSON format.
  --no-opengl-sw                         Do not deploy the software rasterizer
                                         library.
  --no-ffmpeg                            Do not deploy the FFmpeg libraries.
  --list <option>                        Print only the names of the files
                                         copied.
                                         Available options:
                                          source:   absolute path of the source
                                         files
                                          target:   absolute path of the target
                                         files
                                          relative: paths of the target files,
                                         relative
                                                    to the target directory
                                          mapping:  outputs the source and the
                                         relative
                                                    target, suitable for use
                                         within an
                                                    Appx mapping file
  --verbose <level>                      Verbose level (0-2).

Qt libraries can be added by passing their name (-xml) or removed by passing
the name prepended by --no- (--no-xml). Available libraries:
concurrent core core5compat dbus designer designercomponentsInternal
devicediscoverysupportInternal entrypointInternal exampleiconsInternal
fbsupportInternal freetypeInternal gui harfbuzzInternal qthelp jpegInternal
labsanimation labsfolderlistmodel labsqmlmodels labssettings labssharedimage
……

Qt plugins can be included or excluded individually or by type.
To deploy or block plugins individually, use the --include-plugins
and --exclude-plugins options (--include-plugins qjpeg,qsvgicon)
You can also use the --skip-plugin-types or --add-plugin-types to
achieve similar results with entire plugin groups, like imageformats, e.g.
(--add-plugin-types imageformats,iconengines). Exclusion always takes
precedence over inclusion, and types take precedence over specific plugins.
For example, including qjpeg, but skipping imageformats, will NOT deploy qjpeg.

Detected available plugins:

platforms:
    qdirect2d
    qminimal


Arguments:
  [files]                                Binaries or directory containing the
                                         binary.

这其中,–dir参数,可以指定我们部署的目录路径。

比如,我们在c:\build\hello目录下,使用Qt开发编译出了一个world.exe程序,就可以通过如下命令,把world.exe的运行环境部署到c:\build\hello目录中去。

windeployqt.exe c:\build\hello\world.exe --dir c:\build\hello

使用CMake检测依赖库

如果我们使用的CMake做的构建工具,那么还可以在安装的过程中,使用FILE命令来检测程序的依赖库。

CMake的FILE支持很多参数,其中get_runtime_dependencies参数,是专门用来检测程序的运行依赖的。

需要注意的是,使用这个功能,需要满足两点:

  1. CMake需要在3.16版本以上。
  2. 需要使用CMP0087这个Policy的新规则,即需要在CMakeLists.txt里面加上

因为我们构建的是安装脚本,所以把获取动态链接库的这个过程,写在INSTALL命令里面。这样当执行安装的时候,将自动通过FILE的get_runtime_dependencies参数来获取依赖,自动安装。

FILE这个函数的原型为:

 file(GET_RUNTIME_DEPENDENCIES [...])

    New in version 3.16.

    Recursively get the list of libraries depended on by the given files:

    file(GET_RUNTIME_DEPENDENCIES
      [RESOLVED_DEPENDENCIES_VAR <deps_var>]
      [UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>]
      [CONFLICTING_DEPENDENCIES_PREFIX <conflicting_deps_prefix>]
      [EXECUTABLES <executable_files>...]
      [LIBRARIES <library_files>...]
      [MODULES <module_files>...]
      [DIRECTORIES <directories>...]
      [BUNDLE_EXECUTABLE <bundle_executable_file>]
      [PRE_INCLUDE_REGEXES <regexes>...]
      [PRE_EXCLUDE_REGEXES <regexes>...]
      [POST_INCLUDE_REGEXES <regexes>...]
      [POST_EXCLUDE_REGEXES <regexes>...]
      [POST_INCLUDE_FILES <files>...]
      [POST_EXCLUDE_FILES <files>...]
      )

其中,

  • RESOLVED_DEPENDENCIES_VAR <deps_var>
    是找到的依赖的库的列表。

  • UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>
    是没有找到的依赖的库的列表。

  • EXECUTABLES <executable_files>
    是可执行文件,在开启了CMP0087之后,可以使用$<TARGET_FILE:target> 来指代目标编译出来的程序路径。

  • LIBRARIES <library_files>

  • DIRECTORIES 是除了系统目录以外,其它的dll查找路径,可以是一个列表。

  • PRE_EXCLUDE_REGEXES 是执行前排除掉的依赖库的匹配规则

  • POST_EXCLUDE_REGEXES 是执行后排除的依赖库的匹配规则。
    因为很多Windows自带的dll不用再重复部署,这两个参数可以很方便我们排除掉它们。

    下面是一个示例框架,简单地演示了构建一个程序,再通过FILE命令来自动安装依赖的过程:

CMAKE_MINIMAL_REQUIRED (VERSION 3.16)

CMAKE_POLICY (SET CMP0087 NEW)

ADD_EXECUTE(helloworld hellworld.c)

INSTALL(TARGET helloworld DESTINATION ".") 
INSTALL(CODE [[
    LIST(APPEND pre_exclude_regexes "api-ms-.*")
    LIST(APPEND pre_exclude_regexes "ext-ms-.*")
    LIST(APPEND pre_exclude_regexes "ieshims.dll")
    LIST(APPEND pre_exclude_regexes "emclient.dll")
    LIST(APPEND pre_exclude_regexes "devicelockhelpers.dll")

    LIST(APPEND post_exclude_regexes ".*WINDOWS[\\/]system32.*")

    FILE(GET_RUNTIME_DEPENDENCIES
      EXECUTABLES $<TARGET_FILE:helloworld>
      RESOLVED_DEPENDENCIES_VAR _r_deps
      UNRESOLVED_DEPENDENCIES_VAR _u_deps
      PRE_EXCLUDE_REGEXES ${pre_exclude_regexes}
      POST_EXCLUDE_REGEXES ${post_exclude_regexes}
      )
    LIST(LENGTH _u_deps _u_length)
    IF("${_u_length}" GREATER 0)
      MESSAGE(WARNING "Unresolved dependencies detected!")
      FOREACH(_no ${_u_deps})
        MESSAGE(INFO ${_no})
      ENDFOREACH()
    ENDIF()
    FOREACH(_file ${_r_deps})
      MESSAGE("install " ${_file})
      FILE(INSTALL FILES "${_file}"
        DESTINATION "."
        )
    ENDFOREACH()
    ]])
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值