Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
WJCHumble opened this issue Aug 21, 2022 · 0 comments
Open

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

WJCHumble opened this issue Aug 21, 2022 · 0 comments
Labels

Comments

@WJCHumble
Copy link
Owner

前言

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

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

那么,今天本文也会从 CI 基础出发,循序渐进地带着大家认识下 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 & ProfilesApp 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 Profile
  • Mac 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.xcarchive \
-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 文件

接着,则是最后一步验证和分发 .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

结语

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant