意见箱
恒创运营部门将仔细参阅您的意见和建议,必要时将通过预留邮箱与您保持联络。感谢您的支持!
意见/建议
提交建议

macOS App 自动化分发 App Store 探索与实践

来源:恒创科技 编辑:恒创科技编辑部
2022-09-19 20:12:00
前言

提及自动化一词,我想很多同学会想到 CI/CD,这 2 者之间确实是存在一定的联系,可以简单理解成父、子集合之间的关系。

而正如文章标题所言,近期我在研究 macOS App 自动化分发 App Store 的事情,通俗点讲就是希望把原先手动构建 .xcarchive 文件、导出 .pkg 文件以及上传 App Store 的操作转为用 Shell 脚本自动化完成这些步骤。其中,增加的 Shell 脚本会基于现有的 CI/CD 的实现,加入到适当的位置。

那么,今天本文也会从 CI 基础出发,循序渐进地带着大家认识下 macOS App 自动化分发 App Store 实现的所以然。


macOS App 自动化分发 App Store 探索与实践

1 认识持续集成(CI)基础

持续集成,Continuous Integration,简称 CI。这里我们来看下 Wikipedia 上对 CI 的介绍:

—— In software engineering, continuous integration (CI) is the practice of merging all developers' working copies to a shared mainline several times a day. Grady Booch first proposed the term CI in his 1991 method, although he did not advocate integrating several times a day. Extreme programming (XP) adopted the concept of CI and did advocate integrating more than once per day – perhaps as many as tens of times per day.

通常情况下,这在我们实际开发场景中,CI 指的是将项目的构建过程集成到某个单独的软件的实践。例如,在前面所说 macOS App 自动化分发 App Store 的 Shell 实现会加入到现有的 CI/CD 过程,它的 CI 的过程会是这样:

开发人员(构建者)触发 Jenkins 的 Job执行 Job,这会由 Job 所在 Jenkins 的 Slave Node(构建机)执行,生产制品,例如一个 .dmg 或者 .pkg 文件上传制品到制品库

其中,比较关键的则是构建机(Slave Node),我们的整个构建过程的脚本实现都会在构建机上执行,例如后面要讲的自动化分发 App Store 的实现。而这里脚本使用的是 Shell 编写,当然也可以用 Google 的 zx,有兴趣的同学可以自行了解,这里不做展开。

由于,在认识 macOS App 自动化分发 App Store 之前,我们需要先知道 macOS App 手动分发的过程是怎么样的,以便于后续用自动化脚本一一实现手动分发的步骤。

2 手动分发(Distribute)

macOS App 手动分发 App Store 的过程,通俗点讲就是使用 Xcode 提供的 GUI 界面操作完成。但是,在进行正式的操作使用的前提是要有一个可以发布到 App Store 配置完备的 macOS App,这要求你需要满足以下 3 点:

注册成为 Apple Developer Program,在 Apple Store 中下载 Apple Developer,然后在应用的账户中注册成为一个“尊贵的 688 会员”在 https://developer.apple.com/ 后台,分别在 Certificates, Identifiers & Profiles 和 App Store Connect 创建证书相关(Bundle Identifier、Provision Profile、Signing Certificate)和注册 App本地初始化创建一个简单的 macOS App,并关联上前面创建的 Bundle Identifier、Provision Profile、Signing Certificate

关于第 1 点,我想应该没什么难理解的。下面,我们从创建一个 macOS App 出发来串联第 2、3 点要做的事情。

2.1 前置准备(创建一个完备的应用)

创建一个 macOS App 的项目,可以通过 Xcode 快速创建一个,打开 Xcode ——> Create a new Xcode Project ——> 选择创建应用的 Platform(macOS)——> 填写项目名称、Team、Organization Identifier 等信息 ——> 在 General 中选择 App Category 和 App Icons,这里我创建的应用叫 FEKit。

使用 Xcode 打开该项目的 .xcodeproj 文件,或者在终端输入,在我们这个例子则是:

open FEKit.xcodeproj

选择构建的目标,这里我们选择 macOS 作为协议(Scheme)和目标(Target):

配置应用的 Bundle Identifier、Provision Profile、Signing Certificate,这可以在 Apple Developer 后台或者在 Xcode 的 Preferences 中添加 Apple ID 来创建,无论使用其中哪种方式创建的,都会在 Apple Developer 后台的 Certificates, Identifiers & Profiles 中展示:

其中,如果我们要分发 App Store,则需要这 2 个证书:

Mac App Distribution,用于签名分发 App Store 的应用和配置对应的 Provisioning ProfileMac Installer Distribution,用于签名应用的安装包和提交到 App Store

当然,除开这 2 个证书,我们还需要在 Certificates, Identifiers & Profiles 页面的 Identifiers 和 Profiles 中分别创建 App ID 和 Provisioning Profile,这 2 个步骤比较简单(这里不做展开)。

在证书、Indentifiers、Profiles 创建完后,则可以下载证书(.cer)和 Provisioning Profile(.provisionprofile)文件到本地,然后分别双击打开,其中证书则会加载到电脑登录对应的钥匙串(keychain)中,而 Provisioning Profile 则会被 Xcode 使用。并且,值得一提的是每个证书都是加密的,需要配套的密钥来解密使用,也就是一个证书(.cer 文件)对应一个密钥(.p12 文件),这个密钥则是由证书创建者生成的,所以,你在创建证书的时候需要选择一个 .certSigningRequest 文件:

然后在加载证书(.cer 文件)到本地,并且确保有证书对应的密钥( .p12 文件)后,最终登录的钥匙串中的证书会是这样:

2.2 Xcode 手动分发 App Store

接着,我们则可以使用 Xcode 的 Production -> Archive 来构建 .xcarchive 文件:

构建完后,Xcode 会弹出窗口让你选择 Distribute App 或 Validate App:

这里,我们选择 Distribution App -> App Store Connect -> Export -> 选择 Development Team -> Manually manage signing,此时会要求我们选择前面提及的 Distribution 证书、Installer 证书和 Provisioning Profile:

选择 Next -> Export 后,需要选择导出的文件目录,则在该目录下会生成 FEKit.pkg 文件,然后我们可以通过 Transporter 工具来将该文件上传到 App Store Connect(或者前面 Xcode 选择 Export 或 Upload 的时候选择 Upload),之后则可以在 App Store Connect 后台的 TestFlight 查看:

所以,我们通常所说的上传 App Store,指的是上传到 App Store Connect 的 TestFlight,后续再由这里的 App Store 中提交上传文件的审核,审核通过再进行上架的操作。并且,需要注意的是每次上传的 .pkg 文件的版本号都需要比上一次的版本号大一(类似于 NPM 的 Package Version)。

那么,到这里整个手动分发 App Store 的过程就介绍完毕了,总结起来主要是这 3 个步骤:

构建项目生成 .xcarchive 文件导出 .pkg 文件上传 .pkg 文件至 App Store Connect

所以,下面我们需要用 Shell 脚本自动化实现这 3 个步骤,也就是 macOS App 自动化分发 App Store。

3 自动化分发(Distribute)

在介绍 macOS App 自动化分发 App Store 实现之前,我们先来认识这 3 个工具:

xcodebuild 是 Xcode 的一个命令行工具包,主要用于构建项目相关altool 是一个内置于 Xcode 中的命令行工具,用于验证 App 的二进制文件并将其上传至 App Store 或者对 App 进行公证(Notarize)xcrun 也是 Xcode 的一个命令行工具包,主要用于执行 Xcode 相关的工具链,例如 xrun altoolxrun xcode-select

而在接下来讲解 macOS App 自动化分发 App Store 过程中,则会分别提及使用这些工具提供的能力来完成前面的手动步骤。那么,下面就让我们开始逐步认识下自动化的实现过程,首先是构建 .xcarive 文件。

3.1 构建 .xcarchive 文件

我们可以使用 xcodebuild.xcarchive 命令来构建生成 .xcarchive 文件:

xcodebuild -archive \
-scheme "FEKit (macOS)" \
-configuration Release \
-archivePath ./Output/FEKit

可以看到,这里我们使用了 3 个 Option,它们分别的作用:

-scheme 构建的协议,不同的目标 Target 通常对应不同的协议,例如 FEKit (iOS)FEKit (macOS),前者是 IOS,后者是 macOS-configuration 构建的配置,例如 Debug 或 Release,不同的配置对应的证书、签名配置会有不同-archivePath 构建 .xcarchive 导出的目录和文件名,这里则会导出到 Output 目录下并命名为 FEKit.xcarchive

其中,关于项目已有的协议和配置,则可以使用 xcodebuild -list 命令查看。

3.2 导出 .pkg 文件

构建完 .xcarchive 文件后,则需要根据改文件导出 .pkg 文件,这同样可以使用 xcodebuild 提供的 Option 命令完成:

xcodebuild -exportArchive \
-archivePath ./Output/FEKit.xcarchive \
-exportPath ./Output/Pkgs \
-exportOptionsPlist ./Build/ExportOptions.plist

其中,关于 -exportOptionsPlist 则是你导出 .pkg 相关的配置,它会是这样:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>destination</key>
    <string>export</string>
    <key>installerSigningCertificate</key>
    <string>你的 Mac AppStore Install 证书 ID</string>
    <key>manageAppVersionAndBuildNumber</key>
    <true/>
    <key>method</key>
    <string>app-store</string>
    <key>provisioningProfiles</key>
    <dict>
        <key>你的 Bundle ID</key>
        <string>你的 Provisioning Profile 的名称</string>
    </dict>
    <key>signingCertificate</key>
    <string>你的证书 ID</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>teamID</key>
    <string>你的 Team ID</string>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

当然,如果你不想手动创建或填写这些信息,可以用 Xcode 手动导出 .pkg 操作一次,ExportOptions.plist 文件会自动生成在导出的文件目录下。

执行完前面的命令后,导出的 .pkg 文件则会在 -exportPath 配置的文件路径下,在这里也就是在 /Outputs/Pkgs/ 文件目录下。

3.3 验证和分发 .pkg 文件
TODO: 这里可以补充使用 keychain 的方式消费专有密码

接着,则是最后一步验证和分发 .pkg 文件。这一步骤需要使用 xcrunaltool 完成。首先,执行 xcrun altool --validate-app 来验证 .pkg 文件,这个主要用于确认你的应用是否满足上传的条件,例如是否选择 App Category、App Icon 以及版本号递增等,相应的命令则是:

xcrun altool --validate-app \
-f ./Output/Pkgs/FEKit.pkg \
-t macOS \
-u xxxxx \
-p xxxxx \
--show-progress

可以看到,这里使用到了 5 个 Options,它们各自的作用:

-f 需要验证的 .pkg 文件所在文件目录位置-t 验证的目标类型,例如 macOS 或 IOS-u 用于连接 App Store Connect 的 Apple Developer 账号(Apple ID)-p 和账号(Apple ID)对应的 App 专用密码--show-progress 用于输出验证过程的执行情况

其中,关于 -p 的 App 专用密码则需要去Apple ID 后台申请。并且,为了避免将密码明文展示在执行的命令中,我们可以单独维护一个文件来存储账号和密码:

#!/bin/bash
# App Developer 账号(Apple ID)
user="xxxxxxxx"
# App 专用密码
pwd="xxxxxx"

相应地,还需要根据 xcrun altool --validate-app 命令执行的结果(成功或失败)做不同的后续处理:

#!/bin/bash
# app_store_user_pwd.sh 可以单独放到一个隐藏目录,这里只是作为例子所以没有放到隐藏目录
source "./app_store_user_pwd.sh"
echo "Run xcrun altool --validate-app..."
xcrun altool --validate-app --f ./FEKit.pkg -t macOS -u $user -p $pwd --show-progress
if [ $? -eq 0 ]; then
    echo "validate-app success"
    # 执行上传的命令
else
    echo "validate-app fail"
    exit -1;
fi

其中,$? 表示上个命令执行结果,0 表示成功,1 表示失败,所以这里我们使用 if [ $? -eq 0 ]; then 判断验证命令执行结果是否等于 0,是则进行后续上传的处理,不是则输出验证失败的信息并退出。

那么,如果在验证通过 .pkg 文件后,我们则可以上传 .pkg 文件至 App Store Connect,这需要执行 xcrun altool --upload-app 命令:

xcrun altool --upload-app \
-f ./Output/Pkgs/FEKit.pkg \
-t macOS \
-u xxxxx \
-p xxxxx \
--show-progress

可以看到上传的命令和验证的命令使用上大同小异,只有第一个 Option 不同。那么,到这里整个实现自动化分发 App Store 的过程已经介绍完了,由于前面都是分步骤讲解的,所以 Shell 脚本的实现都是分开的,这里我们把上面讲到的 Shell 实现都合并到一个 .sh 文件中:

#!/bin/bash
echo "Run xcodebuild archive..."

xcodebuild archive \
-scheme "FEKit (macOS)" \
-configuration Release \
-archivePath ./Output/FEKit

ARCHIVE_FILE=./Output/FEKit.xcarchive
if [ ! -e "$ARCHIVE_FILE" ]; then
    echo ".xarchive doesn't exist";
    exit -1;
fi

echo "Run xcodebuild -exportArchive..."
xcodebuild -exportArchive \
-archivePath ./Output/FEKit.xcarchive \
-exportPath ./Output/Pkgs \
-exportOptionsPlist ./Build/ExportOptions.plist

PKG_FILE=./Output/Pkgs/FEKit.pkg
if [ ! -e "$PKG_FILE" ]; then
    echo ".pkg doesn't exist";
    exit -1;
fi

source "./Build/app_store_user_pwd.sh"
xcrun altool --validate-app --f $PKG_FILE -t macOS -u $user -p $pwd --show-progress
if [ $? -eq 1 ]; then
    echo "altool validate-app fail"
    exit -1;
fi

echo "altool validate-app success"
xcrun altool --upload-app --f $PKG_FILE -t macOS -u $user -p $pwd --show-progress
if [ $? -eq 0 ]; then
    echo "altool --upload-app success"
else
    echo "altool --upload-app fail"
fi
4 使用 fastlane 自动化分发

fastlane 是一个可以便捷地帮你完成证书管理、代码签名和发布等相关的工具,适用于 iOS、macOS 和 Android 应用。那么,我们也就可以使用 fastlane 来完成上面使用 Shell 脚本实现的自动化分发 App Store 过程。

首先,肯定是安装 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,这里我们需要使用 build_mac_app 和 upload_to_app_store 2 个 Action,前者可以用于构建导出 .pkg 文件,后者可以用于上传 .pkg 文件到 Apple Store Connect:

default_platform(:mac)

platform :mac do
  desc "Description of what the lane does"
  lane :build_upload_appstore do
    # 构建、导出 .pkg 文件
    build_mac_app(
      scheme: "FEKit (macOS)",
      export_method: "app-store",
      output_directory: "./Output/Test",
      output_name: "FEKit",
      export_team_id: "xxxxxx",
      export_options: {
        provisioningProfiles: {
          "com.xxxxxx.xxxx" => "macOSAppStore"
        }
      }
    )
    # 上传 .pkg 到 App Store Connect
    upload_to_app_store(
      pkg: "./Output/Pkgs/FEKit.pkg",
      platform: "osx",
      username: "xxxxxxxxx"
    )
  end
end

其中,在使用 upload_to_app_store 的时候需要注意的是,这里只是声明了你 App Store 的用户名,而专用密码需要预先在系统环境变量中添加 FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD,然后 fastlane 在执行 upload_to_app_store Action 时会去读取该环境变量。

结语

最后,这里我们用一个流程图再回顾下整个自动化分发 App Store 的过程:

并且,我想可能有同学会问,作为前端我们需要懂这个吗?个人认为是需要的,因为在一些场景下,比如说做 React Native 或 Electron 开发的时候,不可避免地就会接触到原生应用的签名、构建、公证(Notarize)和分发 App Store 的概念,所以,通过亲身地体验一番原生应用实现这些的过程还是有一定收益的(知其然使其然)。

如果,文中存在表达不当或错误的地方,欢迎各位同学提 Issue ~

点赞

通过阅读本篇文章,如果有收获的话,可以点个赞,这将会成为我持续分享的动力,感谢~

我是五柳,喜欢创新、捣鼓源码,专注于源码(Vue 3、Vite)、前端工程化、跨端等技术学习和分享,欢迎关注我的微信公众号:Code center
上一篇: 租用美国服务器:潜在的风险与应对策略。 下一篇: MongoDB 5.0 扩展开源文档数据库操作