前语

关于 macOS App 开发者来说,咱们一般情况下可能会挑选在网络上分发 App。可是,站在运用者的角度,假如下载的 App 没有经过 Apple Notary Service 公证(Notarizate)过,这在装置的时分体系则会提示“无法翻开 xxx App,因为无法验证开发者”:

一文读懂,如何工程化实现 macOS App 公证过程

那么,这个时分的解决方法就是修正体系偏好——>安全性与隐私的设置,挑选仍要翻开该 App:

一文读懂,如何工程化实现 macOS App 公证过程

虽然,这样能够让运用者装置 App,可是,这并不是真正在解决这个问题的实质。从根上解决应该是让咱们要分发到网络上的 App 经过 Apple Notary Service 公证(Notarizate),这样一来他人下载装置咱们运用的时分则不会出现无法翻开的提示,而是:

一文读懂,如何工程化实现 macOS App 公证过程

所以,今天本文也将环绕「macOS App 公证」打开怎么经过手动或许自动化(Shell、东西)完结公证(Notarizate)进程。

1. 手动公证

首要,咱们先了解下怎么手动公证?手动公证进程能够经过 Xcode 提供的 GUI 界面操作完结。同样地,首要咱们需求构建 .xarchive 文件:

一文读懂,如何工程化实现 macOS App 公证过程

构建完后,Xcode 会弹出窗口让你挑选 Distribute App 或 Validate App,这儿咱们挑选前者:

一文读懂,如何工程化实现 macOS App 公证过程

接着,不同于之前分发 App Store,咱们需求挑选 Developer ID,它表明的是在 App Store 之外分发 App:

一文读懂,如何工程化实现 macOS App 公证过程

然后,咱们需求挑选 Upload ——> Development Team -> Manually manage signing,此刻需求挑选 Develop ID 对应的 Distribution 证书和 Provisioning Profile:

一文读懂,如何工程化实现 macOS App 公证过程

终究,则挑选 Next ——> Upload,然后则会将咱们的 App 上传到 Apple Notary Service 进行公证,经过则会提示咱们能够分发 App。

假如,了解过 App Store 分发相关的同学应该了解这个手动操作的进程,因为运用 Xcode 公证 App 的进程和 App Store 分发大同小异。所以,在经过简单了解运用 Xcode 手动完结公证进程后,接下来,咱们来知道下怎么自动化完结公证的进程?

2. 自动化公证

自动化公证则指的是咱们经过代码完结前面介绍到的运用 GUI 手动公证的进程。那么,这儿我列出了 4 种现在社区中完结的能够对 macOS App 自动化公证的方法:

  • altool –notarize-app 运用的旧的 Apple Notary Service,适用于 Xcode 12 以及更早的版别,可是需求留意的是Apple 将会在 2023 年秋季停止对它的支撑
  • notarytool 运用的新的 Apple Notary Service,只适用于 Xcode 13 及以上的版别,相比较 altool --notarize-app 快了 10 倍
  • electron-notarize 用 JavaScript 完结的用于公证的东西,支撑 notarytoolaltool --notarize-app 等 2 种公证方法
  • fastlane 用 Ruby 完结的一个能够快捷地帮你完结证书办理、代码签名和发布等相关的东西,适用于 iOS、macOS 和 Android 运用

当然,更进一步的话咱们能够把这个自动化公证也加入到 CI/CD 进程中,有爱好的同学能够自行了解相关完结。所以,接着下面将会对这 4 个的运用做对应的打开介绍,首要是 altool --notarize-app

2.1 altool –notarize-app

altool 是一个内置于 Xcode 中的指令行东西,用于验证 App 的二进制文件并将其上传至 App Store 或许对 App 进行公证(Notarize)。而 altool --notarize-app 也是最早咱们运用的完结自动化公证运用的方法,这在社区中也能够看到大量依据它的实践。

altool --notarize-app 则主要是将运用上传到 Apple Notary Service,可是并不会奉告你公证成功与否,所以一般需求结合 altool --notarization-info 指令一起运用(核对公证成功与否),整个公证的进程会是这样:

一文读懂,如何工程化实现 macOS App 公证过程

能够看到,首要咱们需求运用 altool --notarize-app 将运用上传公证,然后获取本次公证的 UUID:

xcrun altool --notarize-app \
# .app 的压缩包或 .dmg
--file ./Output/Apps/FEKit.zip \
--primary-bundle-id "com.xxxx.xxxx" \
# Apple ID
--username "xxxxx" \
# 运用专用暗码,这能够在 https://appleid.apple.com/account/manage 申请
--password "xxxxx" \

然后,终端中则会输出本次公证上传操作的信息和 RequestUUID,前者用于表明本次操作履行是否成功,后者能够用于 altool --notarization-info 指令查询本次公证进程的信息:

No errors uploading 'Output/Apps/FEKit.zip'.
RequestUUID = xxxxxxx-xxx-xxxx-xxxx-xxxxx

进行完公证上传操作后,Apple Notary Service 则会对本次公证履行相关的操作,而这需求一定的时刻,所以咱们需求(守时)轮询履行 altool --notarization-info 指令实时地获取公证成功失败与否的信息

# 加载 Apple ID($user)和 App 专用暗码($pwd)
source "./Build/app_store_user_pwd.sh"
# 标识公证履行进程成功失败与否,失败 1,0 成功
success=1
i=0
while true; do
    let i+=1
    echo "Checking notarize progress...$i"
    # 获取 altool --notarization-info 履行的输出信息
    process=$(xrun altool --notarization-info $uuid --username $user --password $pwd 2>&1)
    echo "${progress}"
    # 假如前次指令履行成果 $? 不等于 0(表明失败),或许指令输出信息中包括 Invalid
    if [ $? -ne 0 ] || [[ "${progress}" =~ "Invalid"]] then
        echo "Error with notarization. Exiting"
        break
    if
    # 假如指令输出信息中包括 success 表明成功
    if [[ "${progress}" =~ "success"]]; then
        success=0
        break
    else
        echo "Not completed yet. Sleeping for 30 seconds.\n"
    fi
    sleep 30
done
if [ $success -eq 0 ]; then
    echo "Notarize successed."
if

其间,$uuid 则是履行 altool --notarize-app 指令后获取的返回成果:

echo xcrun altool --notarize-app \
--file xxxx \
--primary-bundle-id "com.xxxx.xxxx" \
--username $user \
--password $pwd \
2>&1 | grep RequestUUID |  awk '{print $3}'

这儿咱们来看下咱们比较生疏的 2>&1grep RequestUUIDawk '{print $3}' 等 3 个指令的效果:

  • 2>&1 是为了将规范过错 stderr 输出重定向到规范输出 stdout
  • grep RequestUUID 匹配规范输出中所有包括 RequestUUID 的行
  • awk '{print $3}' 打印出第 3 列的成果,在 RequestUUID = xxxxxxx-xxx-xxxx-xxxx-xxxxx 就是 RequestUUID 的值 xxxxxxx-xxx-xxxx-xxxx-xxxxx
  • | 管道,用于将上个指令的输出经过管道输入到下一个指令

2.2 notarytool

相比较 altool --notarization-info 而言,notarytool 运用起来心智负担少一些,并且快于前者许多,咱们只需求记忆一些 Option,运用一行指令 xcrun notarytool 则能够完结上传公证和进程信息获取的进程:

xcrun notarytool submit ./Output/Apps/FEKit.zip \
# Apple ID
--apple-id $user \
--team-id $teamId \
# 运用专用暗码
--password $pwd \
-v \
-f "json"

其间,--team-id 指的是用户 ID(由数字和字母组成),这能够在本地 KeyChain 的证书中查看(或许 Apple 证书后台),-v--verbose 的缩写,指的是输出公证进程的信息,-f "json" 则是表明终究成果以 JSON 的格局输出,例如:

{
  "path":"\/Users\/wujingchang\/Documents\/project\/demo\/FEKit\/Output\/Apps\/FEKit.zip",
  "message":"Successfully uploaded file",
  "id":"xxxxxxxxxxxxxxxxxxxx"
}

需求留意的是这儿运用的是 Appple ID 和运用专用暗码的方法做与公证服务的恳求认证 Authentication,此外你还能够经过以下 3 种 Option 来进行认证:

  • --keychain-profile <keychain-profile>,运用 xcrun notarytool store-credentials 预先在本地钥匙串中新建一个运用程序暗码,例如叫 AC_PASSWORD,那么在运用 notarytool submit 指令的时分则能够直接运用 --keychain-profile AC_PASSWORD 替代之前的 --apple-id $user --team-id $teamId --password $pwd
  • --keychain <keychain>,不同于前者 --keychain-profile 这儿是输入的 AC_PASSWORD 文件所在的位置
  • --key <key-id> --key-id <key-id> --issuer <issuer>,这运用的是 App Store Connect API keys 的方法进行认证,实质上是生成一个和 App Store Connect 约定好的 JWT,然后每次恳求的时分携带上它,然后经过认证

2.3 electron-notarize

electron-notarize 则是一个用 JavaScript 完结的公证东西,它的原理则是运用的 child_process 履行前面咱们提及的 altool --notarization-infonotarytool 这 2 个指令。

electron-notarize 具名导出了 notarize 函数,咱们只需求运用它以及指定的 Option 则能够完结公证的进程,这儿咱们来看下其函数的完结(伪代码):

// src/index.ts
export async function notarize({ appPath, ...otherOptions }: NotarizeOptions) {
  if (otherOptions.tool === 'notarytool') {
    // ...
    await notarizeAndWaitForNotaryTool({
      appPath,
      ...otherOptions,
    });
  } else {
    // ....
    const { uuid } = await startLegacyNotarize({
      appPath,
      ...otherOptions,
    });
    // ...
    await delay(10000);
    // 获取公证进程信息
    await waitForLegacyNotarize({ uuid, ...otherOptions });
  }
  await stapleApp({ appPath });
}

能够看到,notarize 函数会依据 Option 中传入的 toolnotarytoollegacy 来履行不同的指令来完结公证,这儿前者是 notarytool 后者则是 altool --notarization-info。所以,假如咱们要用 notarytool 的方法进行公证会是这样:

import { notarize } from "electron-notarize"
await notarize({
  appPath: "./Output/Apps/FEKit.zip",
  // Apple ID
  appleId: "xxxxxx",
  // 运用专用暗码
  appleIdPassword: "xxxxxxxx",
  teamId: "xxxxxxxxxxx",
  tool: "notarytool"
})

其间,假如你不希望暗码直接明文暴露在代码中的话,electron-notarize 也支撑了前面咱们说的 --keychain--keychain-profile 等 3 个 Option,你能够依据自己的需求挑选对应的认证方法。

2.4 fastlane

fastlane 是一个能够快捷地帮你完结证书办理、代码签名和发布等相关的东西,适用于 iOS、macOS 和 Android 运用。那么,咱们也就能够运用 fastlane 来完结 macOS App 的公证。

首要,肯定是装置 fastlane,关于这方面的介绍官方文档解说的很是翔实,这儿就不重复论说。而当你装置好 fastlane,则能够在运用项目的根目录履行 fastlane init 来初始化它相关的装备,在初始化的进程会让你挑选运用 fastlane 的方法,这儿咱们挑选手动装备即可。

然后,它会在项目根目录下创立一个 fastlane/Fastfile 目录和文件,后续咱们在履行 fastlane xxx 指令的时分则会依据该文件的代码完结履行详细的操作,默认生成的 Fastfile 文件的装备会是这样:

default_platform(:ios)
platform :ios do
  desc "Description of what the lane does"
  lane :custome_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end

其间,default_platform 用于定义一个默认的渠道 Platform,例如当咱们有 2 个渠道(iOS 和 macOS)的时分,它的的装备需求这样:

default_platform(:ios)
platform :ios do
  desc "Description of what the lane does"
  lane :custome_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end
platform :mac do
  desc "Description of what the lane does"
  lane :custome_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end

此刻,假如咱们履行 fastlane custome_lane,因为这儿渠道默认为 ios,所以则会履行 platorm:ios 下的 custome_lane,反之履行 fastlane mac custome_lane,则是 platform :mac 下的 custome_lane。那么,关于前面咱们这个例子而言只需求 platform:mac

default_platform(:ios)
platform :mac do
  desc "Description of what the lane does"
  lane :custome_lane do
    # add actions here: https://docs.fastlane.tools/actions
  end
end

接着,则能够在 platform:mac 写咱们需求完结的自动化分发 App Store 相关的代码。fastlane 快捷之处在于它完结了许多开箱即用的 Action,这儿咱们需求运用 notarize 这个 Action,它能够用于完结 macOS App 的公证:

default_platform(:mac)
platform :mac do
  desc "Notarizes a macOS app"
  lane :notarize_app do
    notarize(
      package: "./Output/Apps/FEKit.zip",
      use_notarytool: "xcrun notarytool",
      bundle_id: "com.xxxx.xxxx",
      username: "xxxxxxxxxxxxxx",
      verbose: true,
    )
  end
end

其间,在运用 notarize 的时分需求留意的是,这儿仅仅声明了你 App Store 的用户名 username,而专用暗码需求预先在体系环境变量中增加 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD(其他认证方法,有爱好的同学能够自行了解),然后 fastlane 在履行 notarize_app Action 时会去读取该环境变量,然后进行并完结后续的公证进程。

结语

看到这儿,我想可能会有同学会问:“这几种完结公证的东西,挑选哪个比较好?”,这儿比较建议的是挑选 fastlane,因为,除开前面提及它的运用方法非常快捷的长处,它具备的才能也许多,不仅仅能够做 App 公证,还能够做 App Store 分发、证书和版别办理等,所以,挑选 fastlane 将来也能够支撑咱们其他诉求,何乐不为呢?

终究,假如文中存在表达不妥或过错的当地,欢迎各位同学提 Issue ~

点赞

经过阅读本篇文章,假如有收获的话,能够点个赞,这将会成为我持续共享的动力,感谢~

我是五柳,喜爱创新、捣鼓源码,专注于源码(Vue 3、Vite)、前端工程化、跨端等技术学习和共享,欢迎关注我的微信大众号 Code center 或 GitHub