如果你曾经花时间手动发布Flutter应用程序,那么你就已经熟悉这个流程了:团队中的某个人完成某个功能的开发后,会在本地生成APK文件,对其进行签名处理(希望使用正确的密钥库),然后将其上传到指定的位置,并通知质量保障团队。对于iOS版本也是如此,测试环境也是同样的流程,最终部署到生产环境时同样需要重复这些步骤。
然而,在这个整个流程中,往往会出现一些问题:API密钥错误、签名步骤遗漏,或者在某台机器上能够成功构建的应用程序在另一台机器上却会失败等等。
解决这类问题的方法是使用配置良好的CI/CD管道,将这一整套流程从人工操作中解放出来。而在本文中,我们将利用Codemagic来构建这样的管道。
什么是Codemagic?
Codemagic是一个专为移动应用程序设计的CI/CD平台。
与通用型的CI平台不同,Codemagic能够原生地支持Flutter框架。它的构建机器上预先安装了Flutter开发环境,同时提供了针对Apple代码签名的专用支持,并且可以直接与Google Play Store和App Store Connect集成。这样一来,配置的工作量就会大大减少,人们就可以把更多精力集中在真正重要的环节上,也就是应用程序的部署逻辑。
我们将要构建的CI/CD管道涵盖了Android和iOS两个平台上的三个不同阶段:
-
一个“拉取请求审核机制”,用于阻止未经验证的代码进入你的主分支
-
一个测试环境部署流程,该流程会使用真实的环境配置进行构建,生成已签名的应用程序文件,并通过Firebase App Distribution和TestFlight将其发送给测试人员
-
一个生产环境部署流程,该流程会对构建好的应用程序进行混淆处理,将崩溃日志上传到Sentry平台,然后直接提交到Google Play Store和App Store Connect
目录
先决条件
在开始之前,你需要准备以下条件:
-
一个同时具备Android和iOS版本的应用程序
-
一个已连接到你代码仓库的Codemagic账户
-
一个已经配置了App Distribution功能的Firebase项目
-
一个为你的应用程序配置好的Sentry项目
-
一个Google Play Console账户,其中至少需要有一个内部测试渠道可用
-
一个具有App Store Connect访问权限的Apple开发者账户
-
一个拥有所需API权限的Google Play服务账户
-
具备编写Bash脚本的能力
了解Codemagic的YAML配置方式
Codemagic为那些偏好使用图形用户界面的团队提供了可视化的工作流编辑工具——但在这里我们并没有使用它。通过codemagic.yaml文件进行配置,你可以得到经过版本控制、可供审查且能够完全复现的管道定义文件,这些文件会与你的应用程序代码存放在同一位置。对管道进行的任何修改都会像处理其他更改一样,经过相同的Pull Request流程。在团队环境中,这一点非常重要。
该文件应位于你项目的根目录下:
your-flutter-app/
codemagic.yaml
lib/
android/
ios/
scripts/
当构建任务被触发时,Codemagic会检测到这个文件,并根据你定义的规则执行相应的工作流。一个文件即可对应多种工作流,同时适用于所有开发环境——完全避免了重复配置的问题。
管道架构
在开始编写YAML配置文件之前,首先明确管道需要完成的具体任务是非常有必要的。以这样一个拥有三个受保护分支的团队为例:develop、staging和production。每个分支代表着发布生命周期中的不同阶段,而管道会根据被触发的分支类型来执行不同的操作。
以下是这三个开发环境与管道行为的对应关系:
向develop分支提交Pull Request:当开发者针对develop分支提交Pull Request时,系统会启动质量检测工作流。该工作流会执行代码格式检查、静态分析、完整的测试套件测试,并确保代码的覆盖率达到最低要求。只有所有这些测试都通过后,该Pull Request才会被视为合格。
将代码推送到develop或staging分支:当代码被上传到这两个分支中的任意一个时,针对相应平台的构建管道就会被触发。这些管道会识别目标分支,自动配置相应的环境参数(如开发版或测试版的API密钥),生成签名后的应用程序文件,并将其分发到相应的测试渠道:Android应用则通过Firebase App Distribution进行测试,iOS应用则通过TestFlight进行测试。
将代码推送到production分支:当代码最终到达production分支时,构建管道会进入发布模式。此时,生成的代码文件会被进行混淆处理,调试符号会被上传到Sentry平台以便追踪程序崩溃情况,最后生成的应用程序文件会直接提交到Play Store和App Store Connect上进行上线发布。
你的项目结构应该如下所示:
codemagic.yaml
scripts/
generate_config.sh
quality_checks.sh
upload_symbols.sh
lib/
core/
env/
env_ci.dart
env_ci.g.dart
辅助脚本的作用
与其直接在YAML文件中编写复杂的逻辑,不如将这些核心操作委托给三个位于项目根目录下scripts/文件夹中的Bash脚本。这样不仅能保持YAML文件的易读性,更重要的是,你可以在本地机器上运行与持续集成系统完全相同的逻辑,从而彻底解决“在本地机器上可以正常运行,但在集成环境中却出问题”这类问题。
在提交这三个脚本之前,请确保它们都是可执行的:
chmod +x scripts/generate_config.sh
chmod +x scripts/quality_checks.sh
chmod +x scripts/upload_symbols.sh
generate_config.sh
在移动开发中,安全地注入敏感信息是CI/CD流程中最为棘手的问题之一。这里的解决方案是避免直接提交这些敏感信息:实际上只将包含占位符的Dart文件提交到版本控制系统中,而在构建过程中,脚本会用从Codemagic的加密存储系统中获取的实际值来替换这些占位符。
#!/usr/bin/env bash
set -euo pipefail
# 使用方法:./scripts/generate_config.sh ENV_NAME BASE_URL ENCRYPTION_KEY
ENV_NAME=${1:-}
BASE_URL=${2:-}
ENCRYPTION_KEY=${3:-}
TEMPLATE="lib/core/env/env_ci.dart"
OUT="lib/core/env/env_ci.g.dart"
if [ -z "\(ENV_NAME" ] || [ -z "\)BASE_URL" ] || [ -z "$ENCRYPTION_KEY" ]; then
echo "使用方法:$0 <环境名称> <加密密钥>"
exit 2
fi
sed -e "s|>|$BASE_URL|g" \
-e "s|>|$ENCRYPTION_KEY|g" \
-e "s|>|$ENV_NAME|g" \
"$(TEMPLATE)" > "\)OUT"
echo "✅ 已为$ENV_NAME生成配置文件"
工作原理:
set -euo pipefail这一命令能够确保在遇到错误时程序会立即终止。选项-e表示遇到任何失败的操作都会立即退出;选项-u表示如果变量未定义,程序也会退出;而选项-o pipefail则能捕获管道中任意位置的错误,而不仅仅是最后一个命令的错误。在CI流程中,那些未被及时发现的错误可能会导致构建过程出现问题,但这条指令可以有效防止这种情况的发生。
该脚本接受三个参数:环境名称(dev、staging或production)、API基础地址以及加密密钥。使用${1:-}这种语法,如果某个参数缺失,系统会将其默认设置为空字符串,后续的验证机制也会明确检测到这一情况,并输出相应的错误信息,同时以2作为退出码(这通常是表示参数使用不当的常规代码)。
在脚本的核心部分,sed会一次性对模板文件中的三个占位符进行替换,最终将处理结果写入env_ci.g.dart文件中。需要注意的是,这个生成的文件必须被添加到.gitignore文件中,因为它只会在构建过程中存在,或者是在开发人员在本地手动运行脚本后才会出现在他们的机器上。
这两个Dart文件的作用是完全不同的:
env_ci.dart——这个文件会被提交到版本控制系统中,其中只包含占位符:
// lib/core/env/env_ci.dart
class EnvConfig {
static const String baseUrl = "";
static const String encryptionKey = "";
static const String environment = "";
}
env_ci.g.dart——这个文件是在构建过程中生成的,其中包含实际的信息,但永远不会被提交到版本控制系统中:
// lib/core/env/env_ci.g.dart
// 该文件是自动生成的,请勿提交到版本控制系统中
class EnvConfig {
static const String baseUrl = 'https://staging.api.example.com';
static const String encryptionKey = 'sk_live_xxxxx';
static const String environment = 'staging';
}
将这个自动生成的文件添加到.gitignore文件中:
// 生成的环境配置文件
lib/core/env/env_ci.g.dart
quality_checks.sh
这个脚本定义了代码通过质量检查的标准。它运行的每一个检查步骤都像是一道关卡:如果有任何一步失败,脚本会立即停止执行,从而导致构建过程失败。
#!/usr/bin/env bash
set -euo pipefail
echo "🚀 正在运行质量检查"
dart format --output=none --set-exit-if-changed .
flutter analyze
flutter test --no-pub --coverage
if command -v dart_code_metrics >/dev/null 2>&1; then
dart_code_metrics analyze lib --reporter=console || true
fi
echo "✅ 质量检查通过"
每个步骤的作用如下:
dart format --output=none --set-exit-if-changed .: 检查所有Dart文件是否格式正确,且不会修改这些文件。如果有任何文件的格式不符合要求,该命令会以非零码退出,从而导致构建失败。在这里,格式化是必须执行的步骤。
flutter analyze: 对整个项目运行Dart的静态分析工具。它可以检测出空指针安全问题、未使用的导入语句、缺失的await关键字、无用的代码以及各种结构上的问题,从而在这些问题被审查人员发现之前就将其解决。
flutter test --no-pub --coverage: 运行完整的测试套件,并在coverage/lcov.info文件中生成代码覆盖率报告。由于依赖项已经安装好了,因此--no-pub选项会跳过通过pub get来获取依赖项的过程。这个覆盖率报告后续会被用来确保代码质量达到最低标准。
dart_code_metrics这部分是可选的,并且是非阻塞执行的(使用了|| true)。并不是所有环境都安装了这个工具,而且它的检测结果只是建议性的,并不会导致构建失败。等你的团队开始使用这个工具后,你可以删除|| true这部分代码,使其成为强制性的要求。
echo这条命令只有在前面的所有步骤都通过之后才会执行,因为set -e会使得脚本在遇到任何失败时立即退出。如果你在日志中看到了这条命令的执行记录,那就说明这个分支的代码质量是合格的。
upload_symbols.sh
当使用--obfuscate选项编译Flutter的生产版本时,崩溃报告中的堆栈跟踪信息会变得无法阅读。这个脚本用于上传Sentry所需的调试符号文件,这样就可以解除代码混淆,使崩溃报告能够被正常查看。
#!/usr/bin/env bash
set -euo pipefail
RELEASE=${1:-}
[ -z "$RELEASE" ] && exit 2
if ! command -v sentry-cli >/dev/null 2>&1; then
exit 0
fi
sentry-cli releases new "$RELEASE" || true
sentry-cli upload-dif build/symbols || true
sentry-cli releases finalize "$RELEASE" || true
echo "✅ 已为版本$RELEASE上传了调试符号文件"
工作原理:
该脚本接受一个参数:版本标识符。在实际使用中,这个参数始终是Git提交的短SHA值,通过工作流程以`$(git rev-parse –short HEAD)`的形式传递过来。这一机制能够将上传的符号文件、部署后的构建结果以及Sentry中的崩溃报告都与同一个提交版本关联起来,这对于生产环境中的调试工作来说至关重要。
如果环境中没有安装`sentry-cli`,脚本会以`0`的状态退出,而不会出现错误。这样的设计使得符号文件的上传过程能够根据不同的环境进行灵活处理:生产环境会安装`sentry-cli`,而开发环境则可以跳过这一步骤,而不会影响构建流程。
每个`sentry-cli`命令都使用了`|| true`这种逻辑结构来确保系统的稳定性。如果符号文件的上传过程中遇到临时性问题,构建过程仍然应该能够顺利完成,之后可以通过手动方式从存储的文件中重新上传这些符号文件。
releases new命令用于在Sentry中注册新版本;`upload-dif`命令负责将位于`build/symbols`目录中的调试信息文件发送出去(这些文件是使用`–split-debug-info`选项生成的);最后`releases finalize`命令会标记该版本已经部署完成,可以开始收集崩溃报告了。
codemagic.yaml文件的结构
`codemagic.yaml`文件是根据不同的工作流程来组织的。每个工作流程都是一套独立的处理流程定义,包含自己的触发条件、环境配置、构建脚本以及发布目标。在同一份文件中,可以通过`workflows`这个顶层键来管理多个工作流程。
workflows:
pr-quality-gate:
# 在收到拉取请求时触发
# 仅执行质量检查
android-pipeline:
# 在向develop、staging或production环境推送代码时触发
# 负责Android应用的构建与发布工作
ios-pipeline:
# 在向develop、staging或production环境推送代码时触发
# 负责iOS应用的构建与发布工作
每个工作流程都可以自行定义所需的机器类型、环境变量、触发条件以及执行步骤。正是这样的设计使得一份`codemagic.yaml`文件能够发挥出强大的作用:你不需要管理三份独立的配置文件,同时各个构建阶段之间也能保持良好的隔离性。
PR质量检查机制
针对`develop`分支提交的每一份拉取请求,在允许合并之前都必须先通过质量检查。这个工作流程在Codemagic的Linux服务器上执行,因为它不需要为任何平台生成签名文件,只需要验证代码的质量即可。
workflows:
pr-quality-gate:
name: PR质量检查
max_build_duration: 30
instance_type: linux_x2
triggering:
events:
- pull_request
branch_patterns:
- pattern: develop
include: true
source: true
environment:
flutter: stable
scripts:
- name: 安装依赖项
script: flutter pub get
- name: 执行质量检查
script: ./scripts/quality_checks.sh
- name: 检查代码覆盖率
script: |
COVERAGE=\((lcov --summary coverage/lcov.info | grep lines | awk '{print \)2}' | sed 's/%//')
if [ \((echo "\)COVERAGE < 70" | bc) -eq 1 ]; then
echo "测试覆盖率仅为${COVERAGE}%——最低要求为70%"
exit 1
fi
echo "代码覆盖率为${COVERAGE}%——符合要求"
publishing:
email:
recipients:
- your-team@example.com
notify:
success: true
failure: true
让我们来了解一下每个部分的具体功能。
instance_type: linux_x2
Codemagic为不同的工作负载提供了多种类型的机器。对于那些只需要运行Dart工具的质量检查任务来说,Linux机器完全足够使用,而且其成本也远低于macOS实例。只有当构建过程确实需要Xcode时,才应该选择macOS机器。
triggering
Codemagic就是通过这种方式来决定何时执行某个工作流程的。pull_request事件会在任何PR被创建或更新时被触发。branch_patterns配置告诉Codemagic要特别关注那些针对develop分支提交的PR。而source: true这个选项意味着该规则适用于PR的目标分支,而非源分支——因此,任何将PR提交到develop的分支都会触发这个工作流程。
environment
Codemagic提供的支持Flutter的机器上预装了多个版本的Flutter。设置flutter: stable就可以确保工作流程始终使用当前的稳定版本,而无需手动安装SDK。在这方面,Codemagic确实比通用型构建工具更节省配置时间。
质量检查脚本
该工作流程是通过调用quality_checks.sh脚本来执行相关操作的,而不是直接在YAML文件中编写命令。这样的设计使得YAML代码更加易于阅读,同时也确保了开发人员在本地运行该脚本时,能够得到完全相同的结果。这个脚本负责处理格式化、分析以及测试的执行等工作。
代码覆盖率检查
测试完成后,lcov会解析由flutter test --coverage生成的代码覆盖率报告,并提取出具体的行覆盖百分比。如果这个百分比低于70%,构建过程就会失败,并会显示明确的错误信息。这个阈值需要你的团队共同确定,但对于大多数项目来说,70%确实是一个合理的标准。
发布流程
Codemagic内置了电子邮件通知功能。你不需要在CI日志中编写echo命令,只需在工作流程中指定接收邮件的人选,Codemagic就会负责发送通知。无论是成功还是失败的情况,都会被及时告知。
Android构建流程
Android构建流程通过使用Codemagic的环境变量组和条件脚本,在同一个工作流程定义中处理三种不同的环境。具体来说,它会根据触发构建的分支不同,而采取不同的操作方式。
android-pipeline:
name: Android构建与发布
max_build_duration: 60
instance_type: linux_x2
triggering:
events:
- push
branch_patterns:
- pattern: develop
include: true
- pattern: staging
include: true
- pattern: production
include: true
environment:
flutter: stable
android_signing:
- android_keystore
groups:
- staging_secrets
- production_secrets
- firebase_credentials
- sentrycredentials
scripts:
- name: 安装依赖项
script: flutter pub get
- name: 确定当前环境
script: |
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" = "develop" ]; then
echo "ENV=dev" >>> $CM_ENV
elif [ "$BRANCH" = "staging" ]; then
echo "ENV=staging" >>> $CM_ENV
else
echo "ENV=production" >>> $CM_ENV
fi
- name: 生成环境配置文件
script: |
if [ "$ENV" = "dev" ]; then
./scripts/generate_config.sh dev "https://dev.api.example.com" "dev_dummy_key"
elif [ "$ENV" = "staging" ]; then
./scripts/generate_config.sh staging "\(STAGING_BASE_URL)STAGING_API_KEY"
else
./scripts/generate_config.sh production "$(PROD_BASE_URL)PROD_API_KEY"
fi
- name: 构建Android应用程序
script: |
if [ "$ENV" = "production" ]; then
flutter build appbundle --release \
--obfuscate \
--split-debug-info=build/symbols
else
flutter build appbundle --release
fi
- name: 将应用发布到Firebase App Distribution平台
script: |
if [ "$ENV" = "dev" ] || [ "$ENV" = "staging" ]; then
firebase appdistribution:distribute \
build/app/outputs/bundle/release/app-release.aab \
--app "$FIREBASEANDROID_APP_ID" \
--groups "$FIREBASE_groups" \
--token "$FIREBASE_TOKEN"
fi
- name: 提交应用到Play Store
script: |
if [ "$ENV" = "production" ]; then
echo "$GOOGLE_PLAY_SERVICE_ACCOUNT_JSON" > /tmp/service_account.json
flutter pub global activate fastlane 2>/dev/null || true
fastlane supply \
--aab build/app/outputs/bundle/release/app-release.aab \
--json_key /tmp/service_account.json \
--package_name com.your.package \
--track production
fi
- name: 上传Sentry符号文件
script: |
if [ "$ENV" = "production" ]; then
./scripts/upload_symbols.sh $(git rev-parse --short HEAD)
fi
artifacts:
- build/app/outputs/bundle/release/app-release.aab
- build/symbols“
publishing:
email:
recipients:
- your-team@example.com
notify:
success: true
failure: true
以下是各部分的功能及其设计原因。
android_signing
这是Codemagic最实用的功能之一。无需手动解码Base64格式的密钥库并将其写入脚本文件中,您可以直接将密钥库文件上传到Codemagic的加密存储系统中,具体路径为“Teams → Code signing identities → Android keystores”。您只需为该密钥库指定一个参考名称(例如android_keystore),Codemagic会在构建脚本运行之前自动完成解码、存储以及生成key.properties文件的工作。
这一设计有效避免了与签名相关的各种构建错误。
groups
Codemagic允许您在团队设置的环境变量部分将各类敏感信息组织成不同的组。您无需单独声明每条敏感信息,而是可以通过引用这些组来使用它们。这里使用的组包括:
-
staging_secrets:包含STAGING_BASE_URL和STAGING_API_KEY -
production_secrets:包含PROD_BASE_URL和PROD_API_KEY -
firebase_credentials:包含FIREBASE_TOKEN、FIREBASE_ANDROID_APP_ID和FIREBASE_groups -
sentrycredentials:包含SENTRY_AUTH_TOKEN、SENTRY_ORG和SENTRY_Project
通过$CM_ENV进行环境变量检测
Codemagic通过$CM_ENV变量提供了一个特殊的文件路径。如果您在该文件中写入KEY=VALUE这样的内容,那么这个变量将在同一构建过程中的所有后续脚本步骤中被使用。这就是如何将分支名称转换成其他流程组件能够识别的环境变量的。
构建阶段的区分
生产环境的构建会使用--obfuscate和这两个参数;而开发环境和测试环境的构建则会省略这些参数,以便加快编译速度并使本地堆栈跟踪信息更易于阅读。
Firebase应用的发布流程
Firebase CLI负责将开发环境和测试环境的构建版本分发给测试人员。由于Codemagic的Linux服务器上已经预装了Node.js,因此如果您还没有安装Firebase CLI,可以通过npm install -g firebase-tools来安装它;之后也可以通过npx命令来使用它。
应用提交到Play Store
生产环境的应用程序包会通过Fastlane的supply命令上传到Play Store。服务账户的相关信息会从环境变量中读取并保存到一个临时文件中,然后直接传递给Fastlane。请将com.your.package替换为您实际的应用程序ID。
artifacts
“Artifacts”部分用于指定在构建完成后需要保留哪些文件。这些文件可以在Codemagic的构建监控界面中下载获得。此外,调试符号也会被保存在这里,这样在需要重新执行自动化构建流程时,就可以方便地使用这些调试信息了。
iOS构建流程
在Codemagic平台上,iOS项目的构建过程充分展现了该平台的优势。在通用运行环境中进行苹果代码签名时,需要经过一系列复杂的操作,包括使用`security`命令、导入证书以及配置发布配置文件等。而Codemagic通过其内置的签名集成功能,能够自动完成所有这些步骤。
ios-pipeline:
name: iOS构建与发布
max_build_duration: 90
instance_type: mac_mini_m2
triggering:
events:
- push
branch_patterns:
- pattern: develop
include: true
- pattern: staging
include: true
- pattern: production
include: true
environment:
flutter: stable
ios_signing:
distribution_type: app_store
bundle_identifier: com.your.bundle.id
groups:
- staging_secrets
- production_secrets
- app_store_credentials
- sentrycredentials
scripts:
- name: 安装依赖项
script: flutter pub get
- name: 安装Fastlane相关依赖项
script: |
cd ios
gem install bundler --user-install
bundle install
- name: 判断当前环境
script: |
BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$BRANCH" = "develop" ]; then
echo "ENV=dev" >> $CM_ENV
elif [ "$BRANCH" = "staging" ]; then
echo "ENV=staging" >>&> $CM_ENV
else
echo "ENV=production" >>&> $CM_ENV
fi
- name: 生成环境配置文件
script: |
if [ "$ENV" = "dev" ]; then
./scripts/generate_config.sh dev "https://dev.api.example.com" "dev_dummy_key"
elif [ "$ENV" = "staging" ]; then
./scripts/generate_config.sh staging "$(STAGING_BASE_URL)STAGING_API_KEY"
else
./scripts/generate_config.sh production "$(PROD_BASE_URL)PROD_API_KEY"
fi
- name: 构建iOS版本(开发环境,不进行代码签名)
script: |
if [ "$ENV" = "dev" ]; then
flutter build ios --release --no-codesign
fi
- name: 在Staging环境中构建并上传至TestFlight
script: |
if [ "$ENV" = "staging" ]; then
flutter build ipa --release \
--export-options-plist=/Users/builder/export_options.plist
cd ios && bundle exec fastlane beta
fi
- name: 在Production环境中构建并发布至App Store
script: |
if [ "$ENV" = "production" ]; then
flutter build ipa --release \
--obfuscate \
--split-debug-info=build/symbols \
--export-options-plist=/Users/builder/export_options.plist
cd ios && bundle exec fastlane release
fi
- name: 上传Sentry所需的符号文件
script: |
if [ "$ENV" = "production" ]; then
./scripts/upload_symbols.sh $(git rev-parse --short HEAD)
fi
artifacts:
- build/ios/ipa/*.ipa
- build/symbols/**
- /tmp/xcodebuild_logs/*.log
publishing:
app_store_connect:
api_key: $APP_STORE_CONNECT_PRIVATE_KEY
key_id: $APPSTORE_CONNECT_KEY_IDENTIFIER
issuer_id: $APP STORE_CONNECT_ISSUER_ID
submit_to_testflight: true
submit_to_app_store: false
email:
recipients:
- your-team@example.com
notify:
success: true
failure: true
以下是Android开发流程与iOS开发流程之间的不同之处,以及原因说明。
mac_mini_m2
iOS项目的构建需要使用Xcode,因此必须运行在macOS系统上。Codemagic提供了基于Apple Silicon技术的Mac Mini设备。对于Flutter项目和Xcode的开发任务来说,这些设备比基于Intel处理器的机器要快得多,而且Codemagic会根据需求自动配置这些设备,用户无需进行任何基础设施管理。
ios_signing
ios_signing这一环节实际上替代了传统的密钥链设置流程。用户只需将分发证书和配置文件上传到Codemagic的系统中,然后在团队设置中指定相关参数。distribution_type: app_store表示使用App Store的分发签名机制,而bundle_identifier则用于将证书与具体的应用程序关联起来。在脚本执行之前,Codemagic会自动在构建机器上安装这些证书和配置文件。
整个过程中不需要手动运行任何security命令,也不需要创建密钥链或进行Base64解码操作,所有这些步骤都由Codemagic内部完成。
flutter build ipa
在iOS平台上,构建后的输出文件为.ipa格式,而不是.aab格式。当使用flutter build ipa命令并提供相应的export options plist文件时,Xcode会根据该plist文件中的指令来处理签名和打包工作。Codemagic会根据用户设置的ios_signing参数自动生成这个plist文件,并将其保存在/Users/builder/export_options.plist路径下。
Fastlane工作流
Codemagic会通过Bundler将Fastlane工具安装到ios/目录中,然后根据当前环境自动选择相应的工作流程。beta工作流程用于将构建结果上传到TestFlight平台,而release工作流程则用于将应用提交到App Store。
publishing.app_store_connect
Codemagic内置了与App Store Connect平台对接的功能。用户只需在配置文件中指定API凭证,其余步骤都由Codemagic自动完成。submit_to_testflight: true这个选项表示构建完成后,测试版本会自动推送到TestFlight平台上;而对于正式发布版本,则需要将submit_to_app_store参数设置为true。
Xcode日志作为构建结果
路径/tmp/xcodebuild_logs/*.log中存储的原始Xcode构建日志可以作为可下载的文件被保存下来。当iOS项目的构建失败时,如果Codemagic控制面板显示的错误信息不够详细,这些日志就能帮助用户找到问题的真正原因。
环境变量与保密信息的配置方法
所有保密信息都可以在Codemagic的“团队”→“环境变量”设置中进行配置。请将这些信息按逻辑分类,以便在YAML配置文件中清晰地引用它们。
staging_secrets组
| 变量名称 | 描述 |
|---|---|
STAGING_BASE_URL |
测试环境的API基础地址 |
STAGING_API_KEY |
测试环境的API密钥或加密密钥 |
production_secrets组
| 变量 | 描述 |
|---|---|
PROD_BASE_URL |
生产环境的API基础URL |
PROD_API_KEY |
生产环境API密钥或加密密钥 |
firebase_credentials组
| 变量 | 描述 |
|---|---|
FIREBASE_TOKEN |
通过firebase login:ci生成的令牌 |
FIREBASE_ANDROID_APP_ID |
来自Firebase控制台的Android应用ID |
FIREBASE_groups |
用逗号分隔的测试用户组名称 |
app_store_credentials组
| 变量 | 描述 |
|---|---|
APP_STORE_CONNECT_PRIVATE_KEY |
来自App Store Connect的.p8密钥文件的内容 |
APP STORE_CONNECT_KEY_IDENTIFIER |
来自App Store Connect的密钥ID |
APP_STORE_CONNECT_ISSUER_ID |
来自App Store Connect的发行者ID |
GOOGLE_play_SERVICE_ACCOUNT_JSON |
你的Play Console服务账户的完整JSON信息 |
sentry_credentials组
| 变量 | 描述 |
|---|---|
SENTRY_AUTH_TOKEN |
来自Sentry账户设置的认证令牌 |
SENTRY_ORG |
你的Sentry组织名称 |
SENTRYPROJECT |
你的Sentry项目名称 |
对于Android代码签名,请直接将你的密钥库上传到“Teams → Code signing identities → Android keystores”中,而不要将其存储为环境变量。
对于iOS,請将你的分发证书和配置文件上传到“Teams → Code signing identities → iOS certificates”中。
端到端流程
当完整的codemagic.yaml配置就绪后,下面就是典型发布周期中各项操作的完整流程。
开发人员完成某个功能的开发后,会向develop分支提交一个Pull Request。Codemagic会检测到这个请求事件,并在Linux服务器上触发pr-quality-gate工作流程。质量检查脚本会执行格式化、分析、测试以及代码覆盖率检查。如果有任何环节失败,Codemagic会将构建结果标记为失败状态,并向团队发送电子邮件,此时该Pull Request还不能被视为已通过审核。开发人员修复问题后,Codemagic会重新运行检查流程,只有当所有测试都通过后,该Pull Request才会继续进入后续处理阶段。
一旦Pull Request被合并到develop分支中,android-pipeline和ios-pipeline这两个流程会同时启动。它们都会将develop分支作为源代码分支,将其映射到开发环境,插入临时配置信息,生成未签名的发布版本文件,然后将其上传到Firebase App Distribution平台。测试人员可以在合并操作完成后的几分钟内立即使用这些可安装的构建版本进行测试。
develop分支被合并到staging分支后,同样的两个平台构建流程会再次被执行。这一次,系统中会插入真实的配置信息,比如staging环境的API地址和加密密钥。Android版本的软件会使用Codemagic自动管理的密钥库进行签名处理;iOS版本的软件则会通过Fastlane的beta通道上传到TestFlight平台。Codemagic内置的App Store Connect发布工具会负责完成TestFlight上的上传流程。这样一来,质量保障团队就可以使用这些经过正确签名和配置的staging版本来进行测试了。
staging环境被提升为production环境后,构建流程会进入发布模式。此时系统会使用生产环境的配置信息;Android版本的软件在打包时会去除调试符号,并将这些符号保存到build/symbols文件夹中;iOS版本的软件则会在构建过程中启用混淆处理,然后通过flutter build ipa>命令生成IPA文件。这两个平台的构建流程都会调用upload_symbols.sh脚本,该脚本会使用当前的提交哈希值来确保Sentry发布的版本与实际发布的代码完全一致。Android应用的包会通过Fastlane上传到Play Store;iOS应用的IPA文件则会通过Codemagic内置的发布工具上传到App Store Connect。团队会收到相应的成功通知。
这就是整个发布流程的全部环节。整个过程中不需要使用终端命令,也不需要手动执行任何步骤,更不需要在Slack上发送“我认为我已经把staging环境部署好了”这样的消息。
结论
我们刚才构建的这个构建流程覆盖了整个发布生命周期:从自动化的质量检测,到根据不同环境进行配置信息的插入,再到为不同平台生成经过签名的版本文件,以及后续的测试和上传过程——所有这些操作都是通过一个codemagic.yaml文件来完成的。
Codemagic为这个发布流程带来的最大优势在于它与移动生态系统的整合程度更高。密钥库的管理、内置的App Store Connect发布工具、预装好的Flutter开发工具链,以及支持Apple Silicon Mac系统的配置,这些都不是可以额外配置的功能,而是该平台的核心组成部分。因此,这种架构意味着维护工作更少,出现故障的概率更低,而且当出现问题时,也更容易理解整个构建流程的具体运行情况。
你放在scripts/文件夹中的脚本是完全与具体平台无关的。如果你的团队需要将构建流程迁移到其他平台上,这些脚本也可以直接移植过去而无需进行任何修改。YAML配置文件可能会发生变化,但脚本本身的逻辑却不会改变。
最终,你将会得到一个让团队可以信赖的发布流程——当遇到问题时,“部署是否成功?”这个问题会通过通知来得到答案,而不会在Slack上引发讨论。


