跳过主要内容
CI/CD

使用match自动创建Capacitor IOS构建和GitHub动作

使用fastlane和GitHub Actions在5分钟内为您的IOS Ionic应用设置CI/CD管道(2022)

马丁·多纳迪厄

马丁·多纳迪厄

内容营销人员

使用match自动创建Capacitor IOS构建和GitHub动作

使用GitHub Actions在Match中进行自动iOS构建

为Capacitor应用设置CI/CD可能会很复杂和耗时。以下是您需要了解的内容:

我们现在建议使用 Capgo 使用 Capgo CLI 构建 为原生 Capacitor 构建而设计。 本指南是为维护现有 GitHub Actions pipeline的团队保留的Fastlane Match指南,但新iOS构建应使用 Capgo CLI 以免自己维护Fastlane、Match仓库、Xcode runner、证书和上传脚本。

Capgo Build for CI/CD by Capgo

通过 __CAPGO_KEEP_1__ 来实现CI/CD: Capgo Build __CAPGO_KEEP_0__ 构建

  • 从现有的CI/CD pipeline中运行签名的原生iOS构建:: Trigger Capgo Build from GitHub Actions, GitLab CI, Jenkins, or local scripts after your web build and npx cap sync.
  • 触发 __CAPGO_KEEP_0__ 构建从 __CAPGO_KEEP_1__ Actions、GitLab CI、Jenkins 或本地脚本中触发后您的Web构建和从CI密钥中签名:
  • 在您的CI密钥中保留App Store Connect密钥、证书、分发配置文件、密码和团队ID。: Capgo Build 提供维护的 Apple 构建环境,因此您不需要管理 macOS 运行器、Xcode 图像、Fastlane 或 Match 仓库。
  • Artifacts 和提交: 下载已签名的 artifacts 用于 QA 或通过 Capgo CLI 提交发布版本。

定价

  • Capgo 计划从每月 $12 开始
  • 包括 OTA 更新和约 15 个本机构建每月
  • 额外的构建分钟通过分钟计费使用积分

设置 Capgo Build 在 CI/CD

手动设置指南

以下是您需要做的事情:

使用 Fastlane 和 GitHub Actions 的 iOS 持续交付,使用 match

前置条件

继续教程之前…

  • 确保您在开发机器上安装了Fastlane 已安装 您的开发机器上
  • iOS开发者计划会员资格
  • 愿意阅读😆…
  • 一家团队的许多开发者,否则我们建议使用 fastlane cert 更简单的工作流程

关于价格的重要事项

价格GitHub Action

https://github.com/features/actions

该服务在限制内是免费的,具体取决于所选择的机器。
我们将使用一个 macOS 机器,截图中显示了其价格和限制(截止教程创建时间,价格可能会在未来发生变化)

🔴 一旦警告了要求和价格,如果您喜欢,我们继续…

📣 在文章中,我们假设我们已经在 iTunes Connect 中创建了应用,我们有 Apple 生态系统的证书,Fastlane 将复制所有内容!

让我们深入!

本文中的步骤

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

1. 使用App Store Connect API与Fastlane Match

从2021年2月开始,所有用户必须使用两因素认证或两步验证登录App Store Connect。这种额外的安全层对于您的Apple ID有助于确保您是唯一可以访问帐户的人。
来自 Apple Support

使用match进行初始设置需要撤销现有的证书。但是,不用担心,您将直接获得新的证书。

要求

要使用App Store Connect API,Fastlane需要 三个 事项

  1. 发行者ID
  2. 密钥 ID。
  3. 密钥文件或密钥内容。

创建 App Store Connect API 密钥

要生成密钥,您必须在 App Store Connect 中具有管理员权限。如果您没有该权限,请将相关人员指向此文章并遵循以下说明。

1 — 登录到 App Store Connect.

2 — 选择 用户和访问.

App Store Connect 用户访问

3 — 选择

App Store Connect API Integration

App Store Connect API 集成

App Store Connect API keys create

5 — 为密钥输入一个名称。该名称仅供您参考,不是密钥的一部分。

App Store Connect API keys create 名称

6 — 在 Access 下,选择密钥的角色。适用于密钥的角色与适用于您的团队成员的角色相同。请参阅 角色权限。我们建议选择 App 管理员.

7 — 点击生成。

一个 API 密钥的访问权限不能限制到特定的应用。

密钥的名称、密钥 ID、下载链接和其他信息将出现在页面上。

App Store Connect 下载密钥

您可以在这里获取所有三个必要的信息。
<1> 问题 ID。
<2> 密钥 ID。
<3> 点击“下载 API 密钥”以下载您的 API 私有密钥。下载链接仅在私有密钥尚未下载时才会出现。苹果不会保留私有密钥的副本。因此,您只能下载一次。

🔴 将您的私有密钥保存在安全的地方。您不应共享您的密钥、将密钥存储在 code 仓库中或将密钥包含在客户端 code 中。

使用 App Store Connect API 密钥

创建 JWT 认证令牌所需的 API 密钥文件(下载的 p8 文件)、密钥 ID 和发行者 ID。这些信息可以通过多种方式输入到 Fastlane 中,使用 Fastlane 的新动作 app_store_connect_api_key。您可以学习其他方法 Fastlane 文档。我展示此方法,因为我认为它是与大多数 CI 配合工作最方便的方法。您可以设置环境变量。

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

2. 复制 Fastlane 文件

Fastlane 是一款用于自动化移动开发任务的 Ruby 库。使用 Fastlane,您可以配置自定义“道线”,这些道线包含一系列“动作”,这些动作执行您通常使用 Android Studio 执行的任务。您可以使用 Fastlane 做很多事情,但为了完成本教程,我们将仅使用一小部分核心动作。

在项目根目录下创建一个 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 应用的所需证书和配置文件。

创建一个私有的仓库,例如在你的 __CAPGO_KEEP_0__ 个人账户或组织中。 certificates, for example on your GitHub personal account or organization.

然后选择选项 #1 (Git 存储)。

fastlane match init

分配新创建仓库的 URL。

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

现在你在 Fastlane 文件夹中有一个名为

[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>

的文件 Matchfile 应该设置为证书仓库的 HTTPS URL。可选地,您也可以使用 SSH,但这需要一个不同的步骤来运行。 _git_url_接下来,我们要生成证书并在使用 Fastlane Match 时输入您的凭据。

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

当您被要求输入密钥时,请记住它,因为它将在 __CAPGO_KEEP_0__ 动作中用于解密证书仓库。

You will be prompted to enter a passphrase. Remember it correctly because it will be used later by GitHub Actions to decrypt your certificates repository.

fastlane match appstore

如果您在 __CAPGO_KEEP_0__ 和必要的权限方面遇到任何问题,可能这个

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

If you experienced any problem with GitHub and the necessary permissions, maybe this 将有助于您为 Git 生成身份验证令牌。 生成的证书和配置文件将上传到证书仓库资源中

App Store Connect 证书

最后,打开您的

App Store Connect 证书 project 在 Xcode 中,并更新您的应用程序的发布配置的分发配置文件。

XCode 证书

注意事项 💡

MATCH

为了让 CI/CD 能够导入证书和分发配置文件,它需要访问证书仓库。您可以通过生成一个个人访问令牌(应在此之前使用),该令牌具有访问或读取私有仓库的范围来实现。

在 GitHub 中,前往 设置开发者设置个人访问令牌 → 点击 Generate New Token → 勾选 repo 范围 → 然后点击 Generate token.

创建个人访问令牌

请保存生成的个人访问令牌,稍后用于环境变量 GIT_TOKEN.

然后用 Matchfile替换Fastlane文件夹中生成的匹配文件

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用于导入证书和配置文件。 并且GitHub Secrets中的变量将被设置,而不是在文件中硬编码。

构建处理

在GitHub Actions中, 您根据运行CI/CD工作流程所用的分钟数而被计费。 从经验来看,App Store Connect处理构建需要约10-15分钟。

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

如果您对私有项目的定价有同样的担忧,就可以保留 skip_waiting_for_build_processing to true.

什么是陷阱呢?您必须手动更新应用程序在 App Store Connect 中的合规性,才能将构建分发给您的用户。

这是一个可选参数,如果您想为私有项目节省构建分钟数,更新它。如果您是免费项目,完全不用担心。请参阅 定价.

3. 配置GitHub Actions

配置GitHub 秘密

您是否曾经想知道 ENV 的值是从哪里来的?好吧,不是秘密了——它来自您的项目的秘密。 🤦

设置GitHub 秘密

1. APP_STORE_CONNECT_TEAM_ID - 如果您属于多个团队,则是您的 App Store Connect 团队 ID。

2. DEVELOPER_APP_ID - 在 App Store Connect 中,转到应用程序 → 应用程序信息 → 向下滚动到你的应用程序并查找 General Information section 并查找 Apple ID.

3. DEVELOPER_APP_IDENTIFIER - 你的应用程序的捆绑标识符。

4. DEVELOPER_PORTAL_TEAM_ID - 如果你属于多个团队,查找你的开发者门户团队的 ID。

5. FASTLANE_APPLE_ID - 你用来管理应用程序的 Apple ID 或开发者邮箱。

6. GIT_USERNAMEGIT_TOKEN - 你的 Git 用户名和你的个人访问令牌。

7. MATCH_PASSWORD - 初始化匹配时你分配的密钥,用于解密证书和配置文件。

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

9. TEMP_KEYCHAIN_USERTEMP_KEYCHAIN_PASSWORD - 为你的工作流分配一个临时密钥链用户和密码。

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

11. APPLE_ISSUER_ID — App Store Connect API Key 🔺证书颁发者 ID。

12. APPLE_KEY_CONTENT — App Store Connect API Key 🔺证书文件或证书内容。 .p8, 检查它

13. CERTIFICATE_STORE_URL — 匹配密钥的仓库 URL(例如: https://github.com/***/fastlane_match.git)

4. 配置 GitHub 工作流文件

创建一个 GitHub 工作流目录

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

此工作流应在每次 GitHub 后触发 标签, 如果您需要自动化标签,请参阅 自动构建和发布与 GitHub 动作 第一步。

然后,这个工作流程将拉取您的 NodeJS 依赖项,安装它们并构建您的 JavaScript 应用程序。

每次您发送新的提交时,测试飞行中的发布将被构建。

您的应用程序不需要使用Ionic,只需 Capacitor 基础即可,且可以使用旧的Cordova模块,但 Capacitor JS插件应优先使用。

5. 触发工作流程

创建提交

制作 提交, 您应该在仓库中看到正在活动的工作流程。

触发工作流程

推送新提交到分支 maindevelopment 触发工作流程。

使用的提交

几分钟后,构建应该可用在您的 App Store Connect 控制台中。

Testflight 控制台

可以从本地机器部署?

是的,您可以,而且这很容易。

假设您有一个私有仓库,您已经用完了免费计划的分钟数,并且您不想为新发布支付费用,或者您更喜欢手动提交应用程序。

让我们开始吧

好的,我们需要创建 my_project_path/fastlane 路径 a 个名为 .env,Fastfile, 才能创建相同的 机密 在我们的 _GitHub 中找到以下属性:

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

现在,您可以转到 终端 并启动 Fastlane 从您的机器上:

fastlane closed_beta

❌ 关于 .env 文件,因为我们不想暴露此数据,我们必须在我们的 .gitignore,类似于❌

fastlane/*.env

它应该与在远程机器上的 GitHub Actions 发生的事情一样,但是在我们的本地机器上。 🍻

本地Fastlane运行

终端执行:$ Fastlane closed_beta

如果您已经走到这一步,我恭喜您,现在您已经拥有了使用Fastlane和 GitHub Actions来自动化您的iOS应用的完全自动化过程。

每次您发送新的提交,Google Play Console中的beta频道将会构建一个新的发布。 我会根据您的反馈来改进这篇博客,如果您有任何问题或建议,请通过电子邮件联系我 martin@capgo.app

在设备上进行构建

如果您仍需要在设备上进行构建,需要手动将它们添加到配置文件中。 连接您的设备到您的Mac并打开设备菜单 找到设备iOS菜单 然后复制您的标识符 找到标识符iOS 然后启动命令: fastlane register_new_device 它会要求您设置设备名称和标识符: 设置标识符iOS

如果您遇到问题

如果您遇到开发设备无法测试等问题,通常可以通过以下命令解决:

有一个神奇的命令可以帮助您:

fastlane match nuke development
fastlane match development

然后: 清理项目,按住 Shift(⇧)+Command(⌘)+K 或选择 Product > Clean(可能标记为“清理构建文件夹”)

然后尝试在您的设备上再次运行应用。

感谢

本博客基于以下文章:

Capacitor 应用程序的实时更新

当 web 层面的 bug 活跃时,通过 Capgo 将修复推送给用户,而不是等待几天的应用商店审批。用户在后台接收更新,而本机更改仍然在正常的审批路径中。

立即开始

最新博客文章

Capgo 为您提供了创建真正专业的移动应用所需的最佳见解。