在沙盒应用中嵌入命令行工具
macOS 开发

在沙盒应用中嵌入命令行工具

当我们在开发产品时,往往需要将命令行工具整合至应用中(即沙盒化应用) - 我相信许多开发者也有类似的需求。如果你正处于这种状况,那么本篇文章将会极其适合你。我在这里分享了一份详尽的流程步骤,主要参考的是 Apple 的官方教程:《Embedding a command-line tool in a sandboxed app》,并结合了我自己的一些实践经验。我希望这将对你的开发工作带来实质性的帮助!

1,629次点击14分钟阅读

平台

  • macOS 13.5.2
  • Xcode 14.3.1
  • swift 5.8.1

请注意,本文基于以上平台展开,可能会因为版本不同,代码可能需要调整(应该不会很多),请根据实际情况进行修改。

💡 本教程专为准备在 App Store 上架的沙盒应用设计,包含许多文件签名相关内容。即使你的应用并非沙盒应用,也可以参考本文步骤,不过可以跳过对集成命令行工具的签名步骤,因为在上传到 App Connect 时,会对集成的二进制文件进行签名检查。

为什么嵌入命令行工具

  1. 有时我们需要一个独立进程执行某些操作。虽然优选使用 XPC 服务,但嵌入命令行工具往往简单得多。
  2. 为了方便开发,我们可能需要在自己的应用中使用其他构建系统(如make)构造的工具。

总结起来,选择此策略的原因或者是追求便利,或者是别无选择。

新建及配置工程

首次创建工程,选择模板 macOS -> App,点击下一步,命名你的应用,例如 AppWithTools,填写你的组织 ID,这将形成因此你的 App 的 Bundle ID,比如 xxx.xxx.AppWithTools。在此的示例中,它是 studio.5km.AppWithTools

新建项目

设置项目以及 APP Target 的 Deployment Target 属性为具体某个版本。你可以自行设置,只要保证前后的版本一致即可。在此示例中,我设置的版本为 13.0。

💡请记住这个版本号,因为在后续的签名过程中,它会被用到。例如,我们稍后在添加的 Helper Tool Target 中的配置,以及在二进制文件签名的时候都会用到这个版本号。
项目配置 Deployment Target 版本
应用 Target 配置 Deployment Target 版本

在应用目标编辑器的 General 选项卡中,将 App Category 修改为 Utilities。这样在构建应用的分发版本时,就不会出现警告信息了。

配置 App Category

在应用目标编辑器的 Signing & Capabilities 选项卡中,勾选 Automatically manage signing,然后选择合适的开发团队。Signing Certificate 应调整为 Development。这些设置都是为了确保后续能正常进行签名操作。

应用 Target 配置签名

至此,你可以尝试对项目进行一次 Archive 操作(菜单栏选择 Product -> Archive)。如果没有出现警告和错误提示,那么项目的创建就没有问题。完成之后,记得删除刚创建的 archive 包。接下来,让我们继续创建 Helper Tool Target

创建 Helper Tool Target

新建 ToolX

首先,去到项目设置,在左侧边栏左下角点击 + 号,从模板中选择 macOS -> Command Line Tool 来创建新的目标。将其命名为 ToolX,名称可以自行设定,这里用 X 仅为表示我们使用了Xcode进行构建。

新增 Helper Tool Target
新建 ToolX

设置 ToolX 的 Deployment Target 为上面配置的 13.0

ToolX Target 配置 Deployment target

与之前相同,我们需要对签名进行相关的配置,并且需要自行设置 Bundle ID。如果应用程序的 Bundle IDxxx.xxx.AppWithTool,我们就把 ToolX 的 Bundle ID 设置为 xxx.xxx.AppWithTool.ToolX。这个值将成为 Helper Tool 的代码签名标识符。同时,请确保你已添加 Hardened RuntimeApp Sandbox ,尽管这并非上传到 App Connect 的必要条件,但 Apple 认为这是开发应用的最佳实践。

ToolX 签名配置

Build Settings 选项卡中,开启 Skip Install(SKIP_INSTALL)构建设置。若未进行此设定,Xcode会在你的存档中放置一个独立的工具副本(除了嵌入在你的应用中的那一份),这可能在后续试图发布存档时导致问题。

ToolX Build Settings 设置 Skip Install

此外,需要禁用代码签名注入基本权限 (CODE_SIGN_INJECT_BASE_ENTITLEMENTS) 的构建设置。因为该权限与 com.apple.security.inherit 权限不兼容,如果启用,会导致 Xcode 在开发构建中包含 com.apple.security.get-task-allow 权限,从而引发异常。

禁用 CODE_SIGN_INJECT_BASE_ENTITLEMENTS
💡 缺少 com.apple.security.get-task-allow 权限会导致无法调试添加的 Helper Tool。如果你想进行调试,你需要在另一个新项目中进行。然而,这里有一个潜在的问题,即新项目中的调试环境有可能与实际运行环境产生差异,这是无法预知的。具体会出现什么情况,我个人并没有尝试过。

Other Code Signing Flags(OTHER_CODE_SIGN_FLAGS)的构建设置设定为 $(inherited) -i $(PRODUCT_BUNDLE_IDENTIFIER)。这样做是为了确保工具的代码签名标识符与其 Bundle Identifier 匹配。

设置 OTHER_CODE_SIGN_FLAGS

导航器中选择 ToolX.entitlements 并将 com.apple.security.inherit 添加到其中,设置为 YES

添加 com.apple.security.inherit

选择 ToolX Scheme,然后使用快捷键 ⌘+B 进行构建,此步仅是为了确保 ToolX 能否被正确地构建。构建成功后,记得切换回 AppWithTools

尝试 Build

嵌入 ToolX

AppWithTools Target 编辑器的 Build Phases 选项卡中,将 ToolX Target 添加到依赖构建阶段,这样可以确保 Xcode 在构建 AppWithTools 之前,先进行 ToolX 的构建。

添加 ToolX target

添加一个 New Copy Files Phase,然后双击新添加的 Copy Files 名称,将其更改为 Embed Helper Tools(准确的名称并不重要,但建议选取一个描述性的名称)。将 Destination 设置为 Executables。完成这些配置后,在构建打包应用时,会将此处添加的文件复制到应用包的 Contents/MacOS 目录中。

添加 Copy Files
设置 Copy Files

ToolX 构建出的二进制文件添加进该列表,并确保选中了 Code Sign On Copy。这个步骤是为了保证在复制过程中进行文件签名。

添加 ToolX 到文件复制
Code Sign on Copy

构建验证配置

在开始之前,请确保你的应用能够正常上传到 App Connect。有时,可能会有一些协议更新,如果你没有接受这些协议,那么分发应用可能会失败。🤯

按照上述所提及的方式进行 Archive,完成之后 Organizer 窗口会自动打开。如果一切顺利,你将在这里看到刚生成的 App Archive。选中它,然后点击右侧的 Distribute App 按钮。

Distribute App
💡 如果按钮显示为 Distribute Content 而不是 Distribute App,请返回并检查你是否在 ToolX Target 中启用了 SKIP_INSTALL 的构建设置。

继续进行分发流程,记得选择 Export,而不是选择 Upload。在流程的最后,系统会让你选择导出的目录。导出成功后,你会在该目录中找到一个 pkg 的安装包。

Export PKG package

以双击 AppWithTools.pkg 来进行安装。这样,你就会在 /Applications 目录中看到 AppWithTools.app。以下是查看其签名情况的方法:

AppWithTools 签名信息
ToolX 签名信息

所显示的结果分别是 App 本身和嵌入的 ToolX 的签名信息,下面对信息内容进行解读:

  • Identifier:代码签名标识符
  • Format:显示可执行文件是 Universal 的
  • CodeDirectory:在 runtime 字段中, CodeDirectory 标志表示启用了 hardened runtime
  • Authority:该字段显示该代码是由Apple Distribution签名身份签署的,这是提交到 App Store 时所需要的
  • TeamIdentifier:团队ID
  • Entitlements:该应用程序的权限包括 com.apple.security.app-sandbox 以及适用于该应用程序的其他权限

另外,ToolX 不同的是 Entitlements ,只包含 com.apple.security.app-sandboxcom.apple.security.inherit

🚨 如果在运行 ToolX 的过程中 App 出现崩溃,并显示签名错误的提示,那么你可以检查一下 ToolX.entitlements 文件,查看是否添加了除了上述两项之外的其他项。

基于上述操作的基础,对于后续我们在其他构建系统中嵌入工具的步骤就变得很容易理解了。

嵌入由外部构建的命令行工具

现在,我们将以一个C/C++工具为例,通过命令行创建一个 Helper Tool,并将它嵌入到 AppWithTools 中。在实际项目中,我们可能会用到命令行的外部构建系统(如 make)来构建需要嵌入的工具。

当然,你也可以直接使用网上获取的各类二进制工具文件。只需要 macOS 能正常运行该二进制工具文件即可。在这里,我们将编写一个简单的 Hello C 工具进行演示。

构建 Helper Tool

首先,创建一个新的目录,然后在其中新建一个 c 文件 main.c

编辑 main.c 添加如下内容并保存

针对 X86_64Arm64 架构分别编译,并最终合并为 Universal 通用平台的二进制工具,如下:

-mmacosx-version-min 选项用于设置与 AppWithTool 应用程序相匹配的部署目标。对于 Intel 架构,我在上面的配置中设定的是 macOS 13.0;对于 Apple Silicon 架构,同样设置为 macOS 13.0

🎓 对于Apple silicon架构,macOS 11.0 是第一个支持Apple silicon的macOS版本。

签名

这是一个极其重要的步骤🖌️。只要是嵌入到沙盒应用中的外部构建工具,都需要生成 entitlements 权限文件并进行签名。如果缺少这一步骤,最后分发时会出现提示 ToolC 签名错误的问题。

生成权限文件并添加权限项:

使用 codesign 工具签名:

-s - 参数应用了一个临时签名,在这里设置代码签名是非常重要的。在最终发行,将工具嵌入到 App 时,Xcode 将按照上面👆的模式对工具进行重新签名。
-i com.example.apple-samplecode.AppWithTool.ToolC 选项设置了代码签名标识符。签名标识符是唯一的,但并不重要,因为在嵌入过程中,Xcode 会用产品的签名标识符覆盖它,这就是为什么在这里可以使用临时签名。
-o runtime 选项启用了 Hardened Runtime。需要再次强调的是,这对于 App Store 发布并不是必需的,但它对于新代码来说是最佳实践。
--entitlements ToolC.entitlements 选项指定了签名权限。
-f 选项会替换任何现有的签名。这并不是绝对必要的步骤,但它能防止因 clang 对 arm64 架构应用临时签名时可能产生的混淆。

🎓 Apple Silicon 要求对所有代码进行签名,因此在构建适用于 Apple Silicon 的代码时,clang 会自动应用临时签名。

可以使用 codesign 验证签名:

ToolC 签名

将 ToolC 添加到项目中

Xcode 中,右击左侧的项目导航并选择 Add Files To AppWithTools...。在打开的窗口中找到并选择 ToolC,启用 Copy items if needed。在 Added folders 中选择 Create groups。在 Add to targets 选项中取消全选,如下图所示:

添加 ToolC 到项目

完成以上操作后,项目中应该已经包含了 ToolC。接着,我们需要将 ToolC 添加到 Build Phases 中的 Embed Helper Tools项(这一项就是我们前文中添加的 Copy Files)。这样,在构建打包 App 时,就会将嵌入的 Helper Tools 复制到 App 包的 Contents/MacOS 目录中:

添加 ToolC 到 Build Phase

构建并验证

操作步骤类似于前文的 “构建验证配置” 部分。首先进行 Archive,然后在 Organizer 窗口中选择 Distribute App。这次还可以选择 Export。在过程中,会出现签名提示信息,基本上可以认为没有问题了。

ToolC 签名信息

总结

对于嵌入现有二进制文件的情况,最关键的是进行二进制文件的签名和添加到项目的步骤。添加到项目时,不要忘记在 Build Phases 中添加 Copy Files。按照本文的流程进行操作,你基本上会明白哪些步骤是可以省略的。祝你一切顺利

附件📎

源代码如下,供参考:

  1. AppWithTools
  2. ToolC

相关文章