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
您将得到以下输出
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
输出,我们可以看到我们必须将 QtCore 和 QtGui 框架复制到包中。我们假设我们位于构建包的目录中。
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
现在,动态链接器知道在哪里查找 QtCore 和 QtGui。我们必须确保应用程序也知道如何找到库,使用 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 -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 更新 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插件与你的应用程序一起分发。类似于平台插件,每种类型的插件必须位于你的发行目录下的特定子目录(如imageformats
或sqldrivers
)中。
Qt插斂的搜索路径(以及一些其他路径)被硬编码到/path/to/Qt/plugins
。但是,使用预先确定的路径有某些缺点。例如,它们可能不在目标机器上存在。因此,你必须检查各种替代方案以确保找到Qt插斂。
- 使用
qt.conf
。这是建议的方法,因为它提供了最大的灵活性。 - 使用QApplication::addLibraryPath()或QApplication::setLibraryPaths()。
- 使用第三方安装实用程序更改
QtCore 库中的硬编码路径。
《如何创建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. 以及/或全球其他国家的商标。所有其他商标均为各自所有者的财产。