Qt for macOS - 部署

本文档描述了如何创建 macOS 打包文件,并确保应用程序在运行时能够找到所需的资源。我们将以 Qt 源代码包中提供的 Plug & Paint 示例应用程序的部署为例来演示这个过程。

Qt macOS 安装程序包括了部署工具,该工具可以自动化上述过程中的步骤。

打包程序

在 macOS 上,GUI 应用程序必须从打包文件中构建和运行,打包文件是一个在 Finder 中查看时呈现出单个实体的目录结构。应用打包通常包含可执行文件以及所有所需的资源。以下是应用打包结构的快照

打包程序对用户提供了许多优势

  • 因为它作为一个单一实体被识别,所以安装起来很容易。
  • 可以从代码中访问打包信息。

这属于 macOS 特性,超出了本文档的范围。有关打包的更多信息,请见Apple 开发者网站

要使用 CMake 将应用程序构建为打包程序,请将你的可执行目标上的 MACOSX_BUNDLE 属性设置

set_target_properties(plugandpaint PROPERTIES
    MACOSX_BUNDLE TRUE
)

qmake 会自动为你的应用程序生成打包程序。要禁用此功能,请在应用程序的项目文件(.pro)中添加以下语句

CONFIG-=app_bundle

静态链接

如果你想要保持简单,并且只有少数文件要部署,你可以在静态链接库上构建你的应用程序

静态构建 Qt

首先安装 Qt 库的静态版本。请记住,你不能使用插件,你必须使用静态链接构建依赖库,如图像格式、SQL 驱动程序等。

cd /path/to/Qt
./configure -static <other parameters>

通过运行 configure -help,你可以查看可用的各种选项。

将应用程序链接到 Qt 的静态版本

一旦 Qt 已静态构建,下一步是重新生成构建文件并重新构建应用程序。

使用 CMake

确保使用 qt_add_executable 包装命令,它提供了额外的逻辑,例如在静态 Qt 构建中链接 Qt 插件。

为了为 Apple 平台构建,你需要将 cmake_minimum_required() 设置为 3.21.1 或更高版本

cmake_minimum_required(VERSION 3.21.1)

进入包含应用程序的目录

cd /path/to/Qt/examples/widgets/tools/plugandpaint/app

接下来,将 CMAKE_PREFIX_PATH 变量设置为指向你的安装前缀。如果你已经有了 CMake 构建,请删除 CMakeCache.txt 文件。然后,重新运行 CMake

cmake -DCMAKE_PREFIX_PATH=path/to/Qt/6.7.2/your_platform -S <source-dir> -B <build-dir> -G Ninja

或者,使用方便脚本 qt-cmake,它会为你设置 CMAKE_PREFIX_PATH 变量。

path/to/Qt/6.7.2/your_platform/bin/qt-cmake -S <source-dir> -B <build-dir> -G Ninja

最后,进入您的构建目录并运行您选择的构建系统。在这个例子中,我们使用的是Ninja

cd path/to/build/dir
ninja

现在,如果编译和链接过程中没有出现任何错误,您应该有一个准备部署的plugandpaint.app捆绑包。尝试在一个未安装 Qt 或任何 Qt 应用的 macOS 机上安装此捆绑包。

使用 qmake

首先,进入包含应用程序的目录

cd /path/to/Qt/examples/widgets/tools/plugandpaint/app

然后,运行 qmake 为应用程序创建一个新的 makefile,并进行清洁构建以创建静态链接的可执行文件

make clean
qmake -config release
make

您可能希望链接到发布库,您可以在调用 qmake 时指定此操作。您可能想利用“死代码砍伐”来进一步减小二进制文件的大小。您可以通过将 LIBS+= -dead_strip 传递给 qmake,以及在 -config release 参数外进行操作来实现。

同样地,如果一切编译和链接过程中没有出现任何错误,您应该有一个准备部署的 plugandpaint.app 捆绑包。尝试在一个未安装 Qt 或任何 Qt 应用的 macOS 机上安装此捆绑包。

检查链接的库

您可以使用 otool 检查应用程序链接的其他库

otool -L plugandpaint.app/Contents/MacOs/plugandpaint

以下是在静态链接的Plug & Paint中输出的示例

plugandpaint.app/Contents/MacOS/plugandpaint:
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
        (compatibility version 2.0.0, current version 128.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime
        (compatibility version 1.0.0, current version 10.0.0)
/usr/lib/libz.1.dylib
        (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
        (compatibility version 1.0.0, current version 22.0.0)
/usr/lib/libstdc++.6.dylib
        (compatibility version 7.0.0, current version 7.3.0)
/usr/lib/libgcc_s.1.dylib
        (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libmx.A.dylib
        (compatibility version 1.0.0, current version 92.0.0)
/usr/lib/libSystem.B.dylib
        (compatibility version 1.0.0, current version 88.0.0)

如果在输出中看到 Qt 库,这可能意味着您的机器上同时安装了动态和静态的 Qt 库。链接器总是优先选择动态链接。如果您只想使用静态库,您可以

  • 在链接应用程序时将您的 Qt 动态库 ( .dylibs ) 移到另一个目录,然后在完成后移回
  • 或者编辑 Makefile,将 Qt 库的链接行替换为静态库的绝对路径。

例如,将以下内容

-lQtGui

替换为

/where/static/qt/lib/is/libQtGui.a

Plug & Paint 示例由几个组件组成:核心应用 (Plug & Paint)、基本工具附加过滤器 插件。由于我们无法使用静态链接方法部署插件,因此我们迄今为止准备好的捆绑包是不完整的。应用程序将运行,但由于缺少插件,功能将无法使用。要部署基于插件的应用程序,我们应该使用框架方法,这是 macOS 特有的。

框架

在此方法中,确保 Qt 运行时与应用程序捆绑包正确重新分发,并且插件安装在正确的位置,以便应用程序可以找到它们。

使用框架方法在您的应用程序中分发 Qt 有两种方式

  • 应用程序捆绑包中的私有框架
  • 标准框架(或使用安装在二进制文件中的 Qt 框架)

如果您以特殊方式构建 Qt,或者想要确保框架存在,则第一种选项是较好的选择。它只取决于您放置 Qt 框架的位置。

如果您有多个 Qt 应用程序并且希望它们使用单个 Qt 框架而不是多个版本,则第二种选项是较好的选择。

作为框架构建 Qt

我们假设您已经将 Qt 作为框架安装,这是安装 Qt 时的默认设置,位于 /path/to/Qt 目录下。有关如何构建无框架 Qt 的更多信息,请访问 Qt for macOS - 具体问题 文档。

安装时,会设置框架的标识名称。该名称由动态链接器(dyld)用于查找您的应用程序的库。

将应用程序链接到 Qt 作为框架

构建 Qt 作为框架后,我们可以构建 即插即画 应用程序。

使用 CMake

为了为 Apple 平台构建,你需要将 cmake_minimum_required() 设置为 3.21.1 或更高版本

cmake_minimum_required(VERSION 3.21.1)

进入包含应用程序的目录

cd /path/to/Qt/examples/widgets/tools/plugandpaint/app

接下来,将 CMAKE_PREFIX_PATH 变量设置为指向你的安装前缀。如果你已经有了 CMake 构建,请删除 CMakeCache.txt 文件。然后,重新运行 CMake

cmake -DCMAKE_PREFIX_PATH=path/to/Qt/6.7.2/your_platform -S <source-dir> -B <build-dir> -G Ninja

或者,使用方便脚本 qt-cmake,它会为你设置 CMAKE_PREFIX_PATH 变量。

path/to/Qt/6.7.2/your_platform/bin/qt-cmake -S <source-dir> -B <build-dir> -G Ninja

最后,进入您的构建目录并运行您选择的构建系统。在这个例子中,我们使用的是Ninja

cd path/to/build/dir
ninja

现在,如果编译和链接过程中没有出现任何错误,您应该有一个准备部署的plugandpaint.app捆绑包。尝试在一个未安装 Qt 或任何 Qt 应用的 macOS 机上安装此捆绑包。

使用 qmake

首先,进入包含应用程序的目录

cd /path/to/Qt/examples/widgets/tools/plugandpaint/app

运行 qmake 以创建一个新应用程序制作文件,然后进行清洁构建以创建动态链接的可执行文件

make clean
qmake -config release
make

这构建了核心应用程序。使用以下方法构建插件

cd ../plugandpaint/plugins
make clean
qmake -config release
make

现在为 Qt 框架运行 otool,例如 Qt Gui

otool -L QtGui.framework/QtGui

您将得到以下输出

QtGui.framework/QtGui:
/path/to/Qt/lib/QtGui.framework/Versions/4.0/QtGui
        (compatibility version 4.0.0, current version 4.0.1)
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
        (compatibility version 2.0.0, current version 128.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime
        (compatibility version 1.0.0, current version 10.0.0)
/path/to/Qt/QtCore.framework/Versions/4.0/QtCore
        (compatibility version 4.0.0, current version 4.0.1)
/usr/lib/libz.1.dylib
        (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
        (compatibility version 1.0.0, current version 22.0.0)
/usr/lib/libstdc++.6.dylib
        (compatibility version 7.0.0, current version 7.3.0)
/usr/lib/libgcc_s.1.dylib
        (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libmx.A.dylib
        (compatibility version 1.0.0, current version 92.0.0)
/usr/lib/libSystem.B.dylib
        (compatibility version 1.0.0, current version 88.0.0)

对于 Qt 框架,第一行(即path/to/Qt/lib/QtGui.framework/Versions/4/QtGui (兼容版本 4.0.0,当前版本 4.0.1))成为框架的标识名称,该名称由动态链接器(dyld)使用。

但是当您部署应用程序时,您的用户可能没有在指定的位置安装 Qt 框架。因此,您必须提供在协议位置中的框架,或者将框架存储在包中。无论您选择哪种解决方案,您都必须确保框架返回适当的标识名称,并且应用程序查找这些名称。幸运的是,我们可以通过install_name_tool 命令行工具来控制这一点。

install_name_tool 以两种模式工作,-id-change-id 模式用于库和框架,允许我们指定一个新的标识名称。我们使用 -change 模式来更改应用程序中的路径。

让我们通过将 Qt 框架复制到 Plug & Paint 包中来测试一下。查看包的otool 输出,我们可以看到我们必须将 QtCoreQtGui 框架复制到包中。我们假设我们位于构建包的目录中。

mkdir plugandpaint.app/Contents/Frameworks
cp -R /path/to/Qt/lib/QtCore.framework
        plugandpaint.app/Contents/Frameworks
cp -R /path/to/Qt/lib/QtGui.framework
       plugandpaint.app/Contents/Frameworks

首先我们在包内部创建一个 Frameworks 目录。这遵循 macOS 应用程序约定。然后我们将框架复制到新目录中。由于框架包含符号链接,我们使用 -R 选项。

install_name_tool -id @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
       plugandpaint.app/Contents/Frameworks/QtCore.framework/Versions/4.0/QtCore
install_name_tool -id @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui
       plugandpaint.app/Contents/Frameworks/QtGui.framework/Versions/4.0/QtGui

然后运行 install_name_tool 以设置框架的标识名称。 -id 后的第一个参数是新名称,第二个参数是我们想要重命名的框架。文本 @executable_path 是一个特殊的 dyld 变量,告诉 dyld 从可执行文件的位置开始查找。新名称指定这些框架位于 Frameworks 目录的直接子目录中。

install_name_tool -change path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/MacOs/plugandpaint
install_name_tool -change path/to/qt/lib/QtGui.framework/Versions/4.0/QtGui
        @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui
        plugandpaint.app/Contents/MacOs/plugandpaint

现在,动态链接器知道在哪里查找 QtCoreQtGui。我们必须确保应用程序也知道如何找到库,使用 install_name_tool-change 模式。这基本上是通过字符串替换来完成的,以匹配我们之前设置的标识名称。

最后,QtGui 框架依赖于 QtCore,因此我们必须记住更改对 QtGui 的引用

install_name_tool -change path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/Frameworks/QtGui.framework/Versions/4.0/QtGui

在此之后,我们再次运行 otool,并且可以查看应用程序可以找到库。

Plug & Paint 示例的插件让它变得有趣。我们需要遵循以下基本步骤使用插件:

  • 将插件放入捆绑包中,
  • 运行 install_name_tool 检查插件是否使用正确的库,
  • 并确保应用程序知道去哪里查找插件。

我们可以在捆绑包中任何我们想要的位置放置插件,但最佳位置是在 Contents/Plugins 下放。当我们构建 Plug & Paint 插件时,基于其 .pro 文件中的 DESTDIR 变量,插件的 .dylib 文件位于 plugandpaint 目录下的 plugins 子目录中。我们只需将此目录移动到正确的位置。

mv plugins plugandpaint.app/Contents

例如,如果我们对 基本工具 插件的 .dylib 文件运行 otool,我们得到以下信息。

libpnp_basictools.dylib:
libpnp_basictools.dylib
       (compatibility version 0.0.0, current version 0.0.0)
/path/to/Qt/lib/QtGui.framework/Versions/4.0/QtGui
       (compatibility version 4.0.0, current version 4.0.1)
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
       (compatibility version 2.0.0, current version 128.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime
       (compatibility version 1.0.0, current version 10.0.0)
/path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
       (compatibility version 4.0.0, current version 4.0.1)
/usr/lib/libz.1.dylib
       (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
       (compatibility version 1.0.0, current version 22.0.0)
/usr/lib/libstdc++.6.dylib
       (compatibility version 7.0.0, current version 7.3.0)
/usr/lib/libgcc_s.1.dylib
       (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libmx.A.dylib
       (compatibility version 1.0.0, current version 92.0.0)
/usr/lib/libSystem.B.dylib
       (compatibility version 1.0.0, current version 88.0.0)

然后我们可以看到插件链接了构建时它所依赖的 Qt 框架。由于我们希望插件在应用程序捆绑包中使用框架,我们按与应用程序相同的步骤进行更改。例如对于基本工具插件

install_name_tool -change /path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/plugins/libpnp_basictools.dylib
install_name_tool -change /path/to/Qt/lib/QtGui.framework/Versions/4.0/QtGui
        @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui
        plugandpaint.app/Contents/plugins/libpnp_basictools.dylib

我们还必须修改 tools/plugandpaint/mainwindow.cpp 中的代码,将其 cdUp() 修改,以确保应用程序能找到插件。将以下代码添加到 mainwindow.cpp 文件中

#elif defined(Q_OS_MAC)
if (pluginsDir.dirName() == "MacOS") {
    pluginsDir.cdUp();
}
#endif
tools/plugandpaint/mainwindow.cpp 中的额外代码还使我们能够在 Finder 中查看插件,如图像所示。

我们还可以添加扩展 Qt 的插件,例如添加 SQL 驱动或图像格式。我们只需遵循插件文档中概述的目录结构,并确保它们包含在 QCoreApplication::libraryPaths 中。让我们快速以图像格式为例,遵循前面概述的程序。

将 Qt 的图像格式插件复制到捆绑包中

cp -R /path/to/Qt/plugins/imageformats
        pluginandpaint.app/Contents/plugins

使用 install_name_tool 将插件链接到捆绑包中的框架

install_name_tool -change /path/to/Qt/lib/QtGui.framework/Versions/4.0/QtGui
        @executable_path/../Frameworks/QtGui.framework/Versions/4.0/QtGui
        plugandpaint.app/Contents/plugins/imageformats/libqjpeg.dylib
install_name_tool -change /path/to/Qt/lib/QtCore.framework/Versions/4.0/QtCore
        @executable_path/../Frameworks/QtCore.framework/Versions/4.0/QtCore
        plugandpaint.app/Contents/plugins/imageformats/libqjpeg.dylib

更新 tools/plugandpaint/main.cpp 中的源代码以查找新插件。在构造 QApplication 之后,我们添加以下代码

QDir dir(QCoreApplication::applicationDirPath());
dir.cdUp();
dir.cd("plugins");
QCoreApplication::setLibraryPaths(QStringList(dir.absolutePath()));

首先,我们告诉应用程序仅在该目录中查找插件。在我们的情况下,我们希望应用程序仅查找我们与捆绑包一起分发的那些插件。如果我们是更大 Qt 安装的一部分,我们可以使用 QCoreApplication::addLibraryPath()。

警告:在部署插件时,我们对源代码进行更改并将重置应用程序重建时的默认标识名称。因此,您必须重复使用 install_name_tool 将您的应用程序链接到捆绑包中的正确 Qt 框架的过程。

现在,您应该能够将应用程序移动到另一个 macOS 机器并运行它,即使没有安装 Qt。或者,您可以将居住在捆绑包之外的框架移动到另一个目录,看看应用程序是否仍然可以运行。

如果您在捆绑包之外的其他位置存储框架,将应用程序链接到应用程序的技术方法类似;您必须确保应用程序和框架都同意在查找 Qt 库以及插件的地方。

创建应用程序包

当您完成将应用程序链接到 Qt(无论是静态编译还是作为框架)后,应用程序就准备好分发。有关更多信息,请参阅Apple Developer 网站。

虽然部署应用程序的过程确实有一些陷阱,但一旦您了解了各种问题,就可以轻松创建所有 macOS 用户都会喜欢的软件包。

应用程序依赖关系

Qt插件

所有Qt GUI应用程序都需要一个实现Qt平台抽象层(QPA)的插斂。对于macOS,平台插斂的名称为libqcocoa.dylib。此文件必须位于你的发行目录下的特定子目录(默认为platforms)。或者,你还可以调整Qt搜索其插斂的搜索路径,如下所述。

你的应用程序可能还依赖于一个或多个Qt插件,例如JPEG图像格式插件或SQL驱动程序插件。务必将你的应用程序需要的任何Qt插件与你的应用程序一起分发。类似于平台插件,每种类型的插件必须位于你的发行目录下的特定子目录(如imageformatssqldrivers)中。

Qt插斂的搜索路径(以及一些其他路径)被硬编码到QtCore库中。默认情况下,第一个插斂搜索路径将被硬编码为/path/to/Qt/plugins。但是,使用预先确定的路径有某些缺点。例如,它们可能不在目标机器上存在。因此,你必须检查各种替代方案以确保找到Qt插斂。

《如何创建Qt插斂》文档概述了在构建和部署Qt应用程序的插斂时需要注意的问题。

额外的库

你可以使用otool来检查应用程序链接到的库。使用应用路径作为参数运行此命令。

otool -L MyApp.app/Contents/MacOS/MyApp

编译器特定的库很少需要与你的应用程序一起重新分发。但是,有几种部署应用程序的方法,因为Qt可以在macOS上以多种方式进行配置、构建和安装。通常,你的目标可以帮助确定你如何部署应用程序。最后几节描述了部署你的应用程序时必须注意的一些事项。

macOS部署工具

macOS部署工具可以在QTDIR/bin/macdeployqt中找到。它旨在自动化创建一个包含Qt库作为私有框架的可部署应用程序包的过程。

mac部署工具还根据以下规则部署Qt插件(除非使用了-no-plugins选项)

  • 平台插件始终部署。
  • 插件调试版本不部署。
  • 设计器插件不部署。
  • 图像格式插件始终部署。
  • 打印支持插件始终部署。
  • 如果应用程序使用Qt SQL模块,则SQL驱动程序插件被部署。
  • 如果应用程序使用Qt SVG模块,则SVG图标插件被部署。
  • 无障碍插件始终部署。
  • 样式插件始终部署。

重要:如果你的选择是不使用Mac部署工具,你必须确保你的部署包包含这些插斂。

要将第三方库包含在应用程序包中,请在创建包后将库手动复制到包中。

macdeployqt支持以下选项

选项描述
-verbose=<0-3>0 = 无输出,1 = 错误/警告(默认值),2 = 普通,3 = 调试
-no-plugins跳过插件部署
-dmg创建 `.dmg` 光盘镜像
-no-strip不对二进制文件运行 'strip' 命令
-use-debug-libs使用框架和插件的调试版本进行部署(隐含 -no-strip
-executable=<path>让给定的可执行文件也使用部署的框架
-qmldir=<path>部署给定路径中 .qml 文件使用的导入
-qmlimport=<path>将给定路径添加到 QML 导入搜索位置
-always-overwrite即使目标文件存在也要复制文件
-codesign=<ident>运行 codesign,对所有可执行文件使用给定的标识符
-hardened-runtime在签名时启用 Hardened Runtime
-timestamp在签名时包含安全的时间戳(需要互联网连接)
-sign-for-notarization=<ident>激活对签名的必要选项(需要互联网连接)。激活的选项有 -hardened-runtime-timestamp-codesign=<ident>
-appstore-compliant跳过使用私有 API 的组件的部署
-libpath=<path>将给定路径添加到库搜索路径
-fs=<filesystem>设置用于 .dmg 光盘镜像的文件系统(默认为 HFS+)

注意:macOS High Sierra 引入了新的苹果文件系统 (APFS)。旧版本的 macOS 无法读取使用 APFS 格式的 .dmg 文件。默认情况下,macdeployqt 使用旧的 HFS+ 文件系统以与 Qt 当前支持的所有版本的 macOS 兼容。使用 -fs 选项指定不同的文件系统。

卷名

使用 -dmg 创建的磁盘镜像的卷名(在打开的 .dmg 文件窗口标题中显示的文本)基于运行 macdeployqt 时应用程序的路径。例如,考虑以下为 Qt Quick 应用程序创建磁盘镜像的命令

macdeployqt /Users/foo/myapp-build/MyApp.app -qmldir=/Users/foo/myapp/qml -dmg

生成的卷名将是

/Users/foo/myapp-build/MyApp.app

为确保卷名只包含应用程序名称,而不是部署机器上的路径,请在同一目录中运行 macdeployqt

cd /Users/foo/myapp-build
macdeployqt MyApp.app -qmldir=/Users/foo/myapp/qml -dmg

生成的卷名然后将是

MyApp.app

© 2024 Qt 公司有限公司。此处包含的文档贡献是各自所有者的版权。此处提供的文档根据 Free Software Foundation 发布的 GNU 自由文档许可证版本 1.3 的条款提供。Qt 和相关标志是芬兰的 Qt 公司 Ltd. 以及/或全球其他国家的商标。所有其他商标均为各自所有者的财产。