跳转至正文
CI/CD

使用 match 和 GitHub actions 实现 Capacitor iOS 自动化构建

5 分钟上手:使用 fastlane 和 GitHub Actions 为你的 iOS Ionic 应用搭建 CI/CD 流水线 (2022)

使用 Match 和 GitHub Actions 实现 Capacitor iOS 自动化构建

使用 Match 和 GitHub Actions 实现 iOS 自动化构建

为 Capacitor 应用配置 CI/CD 可能非常复杂且耗时。以下是您需要了解的内容:

我们现在推荐使用 使用 Capgo CLI 进行 Capgo 构建 用于原生 Capacitor 构建。 本 Fastlane Match 指南仅供维护现有 GitHub Actions pipeline 的团队参考。对于新的 iOS 构建,建议使用 Capgo CLI,这样你就无需再手动维护 Fastlane、Match 仓库、Xcode runner、证书以及 upload 脚本。

通过 Capgo 实现 Capgo CI/CD 构建

无需维护 Fastlane Match、Xcode runner、证书、provisioning profile 和 upload 脚本。 Capgo 构建 通过现有的 CI/CD pipeline 运行已签名的原生 iOS 构建:

  • 完美适配您的 pipeline: 在 Web 构建完成后,通过 GitHub Actions、GitLab CI、Jenkins 或本地脚本触发 Capgo Build,并且 npx cap sync.
  • 使用 CI secrets 进行签名: 将 App Store Connect 密钥、证书、provisioning profiles、密码和 Team ID 保存在您自己的 CI secrets 中。
  • 无需维护原生 runner: Capgo Build 提供维护良好的 Apple 构建环境,您无需再亲自管理 macOS runner、Xcode 镜像、Fastlane 或 Match 仓库。
  • 构建产物与提交: 下载已签名产物用于 QA,或通过 Capgo CLI 提交发布构建。

定价

  • Capgo 方案起售价为 $12/月
  • 包含 OTA 更新,每月约 15 次原生构建
  • 超出部分的构建时长将通过积分按分钟计费

在 CI/CD 中配置 Capgo Build

手动配置指南

你需要执行以下操作:

使用 Fastlane 和 GitHub Actions (match) 实现 iOS 的持续交付

前提条件

在继续本教程之前…

  • 请确保你已安装 Fastlane 已安装 在你的开发机上。
  • iOS 开发者计划会员资格。
  • 愿意阅读 😆…
  • 团队中有多名开发者,否则我们建议使用 fastlane cert 以简化工作流。

关于价格的重要说明

GitHub Action 价格

https://github.com/features/actions

该服务在限额内是“免费”的,具体取决于所选的机器类型。
我们将使用一台 macOS 机器,你可以在截图中看到其价格和限制(价格为教程编写时的价格,未来可能会有变动)

🔴 了解完需求和价格后,如果你准备好了,我们就继续……

📣 本文假设我们已在 iTunes Connect 中创建了 App,并拥有 Apple 生态系统的证书,一切都将由 Fastlane 自动完成!

让我们开始吧 🤿

本文的操作步骤

  1. 使用 Fastlane Match 配置 App Store Connect API
  2. 环境要求
  3. 创建 App Store Connect API Key
  4. 使用 App Store Connect API 密钥
  5. 复制 Fastlane 文件
  6. 配置 Fastlane match

1. 使用 Fastlane Match 配置 App Store Connect API

自 2021 年 2 月起,所有用户登录 App Store Connect 时都必须进行双重身份验证或两步验证。这种为您的 Apple ID 提供的额外安全层,可以确保只有您本人能够访问您的账户。
来自 Apple 支持

开始使用 match 需要您撤销现有的证书。但不用担心,您会直接获得新的证书。

环境要求

为了能够使用 App Store Connect API,Fastlane 需要 三 项内容。

  1. Issuer ID。
  2. Key ID。
  3. Key 文件或 Key 内容。

创建 App Store Connect API Key

要生成 Key,您必须拥有 App Store Connect 的 Admin 权限。如果您没有该权限,可以将本文转发给相关人员并按照以下步骤操作。

1 — 登录 App Store Connect.

2 — 选择 用户与访问权限.

App Store Connect 用户访问权限

3 — 选择 Integration 选项卡。

App Store Connect API 集成

4 — 点击 Generate API Key 或 (+) 按钮。

在 App Store Connect 创建 API 密钥

5 — 为密钥输入一个名称。该名称仅供您参考,不属于密钥本身。

App Store Connect API 密钥创建名称

6 — 在“访问权限”下,选择密钥的角色。密钥适用的角色与您团队中用户的角色相同。请参阅 角色权限。我们建议选择 应用管理器.

7 — 点击“生成”。

API 密钥的访问权限无法限制在特定 App 内。

新密钥的名称、密钥 ID、下载链接及其他信息将显示在页面上。

在 App Store Connect 下载密钥

您可以在此处获取所有三项必要信息。
<1> Issue ID。
<2> Key ID。
<3> 点击 “Download API Key” 下载您的 API 私钥。只有在私钥尚未被下载时,下载链接才会显示。Apple 不会保留私钥的副本。因此,您只能下载一次。

🔴 请将私钥妥善保管。切勿共享您的密钥,不要将密钥存储在 code 仓库中,也不要将密钥包含在客户端 code 中。

使用 App Store Connect API 密钥

创建用于授权的 JWT token 需要 API Key 文件(您下载的 p8 文件)、Key ID 以及 Issuer ID。可以通过 Fastlane 的新 action 以多种方式将这些信息输入到 Fastlane 中, app_store_connect_api_key。您可以在 Fastlane 文档中了解其他方式。我展示这种方法是因为我认为对于大多数可以通过设置环境变量来运行的 CI 来说,这是最简单的方式。

现在我们可以使用 App Store Connect API key 来管理 Fastlane 了,太棒了!

2. 复制 Fastlane 文件

Fastlane 是一个旨在自动化常见移动开发任务的 Ruby 库。通过 Fastlane,您可以配置自定义的 “lanes”,它们将一系列 “actions” 组合在一起,执行您通常在 Android Studio 中手动完成的任务。Fastlane 功能强大,但为了本教程的目的,我们仅使用少数几个核心 actions。

在项目根目录创建 Fastlane 文件夹,并复制以下文件: Fastfile

default_platform(:ios)

DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]

def delete_temp_keychain(name)
  delete_keychain(
    name: name
  ) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end

def create_temp_keychain(name, password)
  create_keychain(
    name: name,
    password: password,
    unlock: false,
    timeout: 0
  )
end

def ensure_temp_keychain(name, password)
  delete_temp_keychain(name)
  create_temp_keychain(name, password)
end

platform :ios do
  lane :build do
    build_app(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )
  end
  lane :refresh_profiles do
    match(
      type: "development",
      force: true)
    match(
      type: "adhoc",
      force: true)
  end
  desc "Register new device"
  lane :register_new_device do  |options|
      device_name = prompt(text: "Enter the device name: ")
      device_udid = prompt(text: "Enter the device UDID: ")
      device_hash = {}
      device_hash[device_name] = device_udid
      register_devices(
                       devices: device_hash
                       )
    refresh_profiles
  end
  lane :closed_beta do
    keychain_name = TEMP_KEYCHAIN_USER
    keychain_password = TEMP_KEYCHAIN_PASSWORD
    ensure_temp_keychain(keychain_name, keychain_password)

    api_key = app_store_connect_api_key(
      key_id: APPLE_KEY_ID,
      issuer_id: APPLE_ISSUER_ID,
      key_content: APPLE_KEY_CONTENT,            
      duration: 1200,            
      in_house: false
    )

    match(
      type: 'appstore',
      git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
      readonly: true,
      keychain_name: keychain_name,
      keychain_password: keychain_password,
      api_key: api_key
    )

    gym(
      configuration: "Release",
      workspace: "./ios/App/App.xcworkspace",
      scheme: "App",
      export_method: "app-store",
      export_options: {
        provisioningProfiles: { 
            DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
        }
      }
    )

    pilot(
      apple_id: "#{DEVELOPER_APP_ID}",
      app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
      skip_waiting_for_build_processing: true,
      skip_submission: true,
      distribute_external: false,
      notify_external_testers: false,
      ipa: "./App.ipa"
    )

    delete_temp_keychain(keychain_name)
  end
  lane :submit_review do
    version = ''
    Dir.chdir("..") do
      file = File.read("package.json")
      data = JSON.parse(file)
      version = data["version"]
    end
    deliver(
      app_version: version,
      submit_for_review: true,
      automatic_release: true,
      force: true, # Skip HTMl report verification
      skip_metadata: false,
      skip_screenshots: false,
      skip_binary_upload: true
    )
  end
end

Appfile

app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])

配置 Fastlane match

Fastlane match 是 iOS code 签名的一种新方式。Fastlane match 让团队能够轻松管理 iOS 应用所需的证书和 provisioning profiles。

创建一个名为以下的私有仓库 certificates,例如在您的 GitHub 个人账号或组织下。

为您的 iOS 应用初始化 Fastlane match。

fastlane match init

然后选择选项 #1 (Git Storage)。

[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?

填入新创建的仓库 URL。

[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>

现在,您的 Fastlane 文件夹中已包含名为 Matchfile 和 _git_url_应设置为证书仓库的 HTTPS URL。你也可以选择使用 SSH,但这需要额外的运行步骤。

# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")

接下来,使用 Fastlane Match 生成证书,并在提示时输入你的凭据。

系统会提示你输入密码。请务必记牢,因为稍后 GitHub Actions 需要用它来解密你的证书仓库。

fastlane match appstore

如果一切顺利,你应该会看到如下内容:

[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌

如果你在 GitHub 及相关权限方面遇到问题,也许这篇 文章 能帮你生成 git 身份验证 token。

生成的证书和 provisioning profiles 会被上传到证书仓库资源中

App Store Connect 证书

最后,打开你的 project 在 Xcode 中,并更新您 App release 配置的 provisioning profile。

Xcode 证书

注意事项 💡

MATCH

为了让 CI/CD 能够导入证书和 provisioning profiles,它需要拥有证书仓库的访问权限。您可以生成一个具有访问或读取私有仓库权限的 personal access token(之前应该用过)。

在 GitHub 中,前往 设置 → 开发者设置 → 个人访问令牌 (Personal access tokens) → 点击 Generate New Token → 勾选 repo scope → 然后点击 Generate token.

创建 Personal access token

保留一份已生成的 personal access token。稍后你会将其用于环境变量 GIT_TOKEN.

然后将你在 Fastlane 文件夹中生成的 match 文件替换为 Matchfile

CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]

git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)

此文件将由 GitHub Actions 用于导入证书和 provisioning profiles。 而变量将设置在 GitHub Secrets 中,而不是在文件中硬编码它们。

构建处理中

在 GitHub Actions 中, 费用按分钟计费 即你运行 CI/CD workflow 所消耗的分钟数。根据经验,构建在 App Store Connect 中处理完成大约需要 10–15 分钟。

对于私有项目,每次构建的预估成本最高可达 $0.08/分钟 x 15 分钟 = $1.2,甚至更高,具体取决于项目的配置或依赖项。

如果你和我一样对私有项目的定价有同样的顾虑,可以继续使用 skip_waiting_for_build_processing 至 true.

有什么代价吗?在构建完成后,你必须手动在 App Store Connect 中更新应用的合规性,才能将构建版本分发给用户。

这只是一个可选参数,如果你想为私有项目节省构建时长,可以进行更新。对于免费项目,这完全不是问题。请参阅 定价.

3. 设置 GitHub Actions

配置 GitHub secrets

有没有想过 ENV 这些值的来源?其实这不再是秘密了——它们来自你项目的 secret。🤦

设置 GitHub secrets

1. APP_STORE_CONNECT_TEAM_ID - 如果你属于多个团队,请填写你在 App Store Connect 中的团队 ID。

2. DEVELOPER_APP_ID - 在 App Store Connect 中,前往应用 → 应用信息 → 向下滚动到应用的 General Information 部分,并查找 Apple ID.

3. DEVELOPER_APP_IDENTIFIER - 应用的 bundle identifier。

4. DEVELOPER_PORTAL_TEAM_ID - 如果你属于多个团队,请查找 Developer Portal 团队 ID。

5. FASTLANE_APPLE_ID - 用于管理应用的 Apple ID 或开发者邮箱。

6. GIT_USERNAME &amp; GIT_TOKEN - 你的 git 用户名和 personal access token。

7. MATCH_PASSWORD - 初始化 match 时设置的 passphrase,用于解密证书和 provisioning profiles。

8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>,例如。 match AppStore com.domain.blabla.demo.

9. TEMP_KEYCHAIN_USER &amp; TEMP_KEYCHAIN_PASSWORD - 为你的 workflow 分配一个临时的 keychain 用户名和密码。

10. APPLE_KEY_ID — App Store Connect API Key 🔺Key ID。

11. APPLE_ISSUER_ID — App Store Connect API Key 🔺Issuer ID。

12. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 Key 文件或 Key 内容 .p8, 进行检查

13. CERTIFICATE_STORE_URL — 你的 Match keys 仓库 URL (例如: https://github.com/***/fastlane_match.git)

4. 配置 GitHub workflow 文件

创建一个 GitHub workflow 目录。

cd .github/workflows

在 workflow 文件夹内,创建一个名为 build-upload-ios.yml的文件并添加以下内容。

name: Build source code on ios

on:
  push:
    tags:
      - '*'

jobs:
  build_ios:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v6
      - name: set Node.js
        uses: actions/setup-node@v6
        with:
          node-version: '24'
          cache: npm
      - name: Install dependencies
        id: install_code
        run: npm ci
      - name: Build
        id: build_code
        run: npm run build
      - uses: actions/cache@v5
        with:
          path: ios/App/Pods
          key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-pods-
      - name: Sync
        id: sync_code
        run: npx cap sync
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.7.2
      - uses: maierj/fastlane-action@v2.3.0
        env:
          DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
          PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
          TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
          TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
          APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
          APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
          APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
          CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
          GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
          GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
          DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
        with:
          lane: closed_beta
      - name: Upload release bundle
        uses: actions/upload-artifact@v2
        with:
          name: ios-release
          path: ./App.ipa
          retention-days: 60

该 workflow 应在每次 GitHub 后触发 标签,如果需要自动化标签管理,请参考 使用 GitHub Actions 实现自动构建与发布 。

随后,该工作流将拉取 NodeJS 依赖、进行安装并构建您的 JavaScript 应用。

每次您推送新的 commit 时,系统都会在 TestFlight 中构建一个 release。

您的 App 无需使用 Ionic,只需 Capacitor 基础环境即可。虽然可以保留旧的 Cordova 模块,但建议优先使用 Capacitor JS 插件。

5. 触发工作流

创建 Commit

进行一次 commit,您应该就能在仓库中看到正在运行的工作流了。

触发工作流

将新提交推送到分支 main 或 development 以触发工作流。

起始于提交

几分钟后,构建版本即可在 App Store Connect dashboard 中查看。

TestFlight Dashboard

可以从本地机器部署吗?

可以,而且非常简单。

假设你有一个私有仓库,且免费额度的时长已用完,你不想为新版本付费,或者你更倾向于手动提交应用。

让我们开始吧

好的,首先我们需要在 my_project_path/fastlane 指定一个名为 .env 的文件, 只需将其放在与 Fastfile 相同的路径下, 即可创建与 我们 _GitHub 中相同的 secret 属性,如下所示:

.env 文件(用于本地机器部署)

现在,你可以前往 终端 并运行 Fastlane 在你的本地机器上:

fastlane closed_beta

❌ 关于 .env 文件的重要提示:为了避免泄露敏感数据,我们必须将其添加到 .gitignore文件中,如下所示:❌

fastlane/*.env

它的运行逻辑与在远程机器上通过 GitHub Actions 运行完全一致,只是在本地执行。🍻

本地运行 Fastlane

终端执行:$ Fastlane closed_beta

如果你已经看到这里,恭喜你!现在你已经通过 Fastlane 和 GitHub Actions 为你的 iOS 应用实现了一套全自动化的流程。

每次你提交新的 commit,Google Play 控制台的 beta 渠道都会自动构建一个新版本。 我会根据大家的反馈不断改进这篇博客,如果你有任何问题或建议,请通过邮件联系我。 martin@capgo.app

在设备上构建

如果你仍需在设备上构建,则需要手动将它们添加到 provisioning 中。 将设备连接到你的 Mac 并打开设备菜单 找到 iOS 设备菜单 然后复制你的标识符 找到 iOS 标识符 然后运行以下命令: fastlane register_new_device 系统会提示你设置设备名称和标识符: 设置 iOS 标识符

如果遇到问题

如果你遇到开发设备无法测试等问题,通常这能解决。

有一个能救你的神奇命令:

fastlane match nuke development
fastlane match development

然后: 按下 Shift(⇧)+Command(⌘)+K 或选择 Product > Clean(可能显示为 “Clean Build Folder”)来清理项目

接着尝试在设备上重新运行 App。

谢谢

本文参考了以下文章:

进阶指南:使用 match 实现自动 Capacitor IOS build 与 GitHub actions

如果你正在使用 使用 match 实现自动 Capacitor IOS build 与 GitHub actions 要规划 CI/CD 自动化,请将其与 Capgo CI/CD 用于 Capgo CI/CD 中的产品工作流, Capgo Native Build 用于 Capgo Native Builds 中的产品工作流, Capgo 集成 用于 Capgo 集成中的产品工作流, CI/CD 集成 用于 CI/CD 集成中的实现细节,以及 GitHub Actions 集成 有关 GitHub Actions 集成的实现细节。

为 Capacitor 应用提供实时更新

当 Web 层 Bug 已上线时,通过 Capgo 推送修复,不必等 App Store 审核数天。用户会在后台获取更新,而原生变更仍走常规审核流程。

博客最新文章

Capgo 为你提供打造真正专业移动应用所需的深度洞察。