# 利用 GitHub Actions 进行 iOS 项目的 CI/CD **Published by:** [Untitled](https://paragraph.com/@0x74cdebd6028e5d8407c2ad6f13bedd9892e3fb2a/) **Published on:** 2021-12-09 **URL:** https://paragraph.com/@0x74cdebd6028e5d8407c2ad6f13bedd9892e3fb2a/github-actions-ios-ci-cd ## Content 搞 iOS 以来,一直在想找一个云主机进行 iOS 项目的 CI 探索,方向想偏了,没找到几家提供 macOS 系统的云主机,遂作罢。 最近在读 《iOS App Distribution & Best Practices》,里面提到现在有大致三类 CI 服务商: Full-service CI,即全方位服务的 CI 提供商,可以理解为通过交互页面就可以进行部署。虽然简单,但可能不太容易拓展。典型如: Bitrise: https://www.bitrise.io/ Microsoft’s App Center: https://appcenter.ms/ Managed CI,即在云上替你托管硬件相关的工作,你只需要提供构建脚本,在 iOS 项目中,常见的就是构建方式就是 fastlane 和 xcodebuild。比较流行的如: CircleCI: https://circleci.com/ GitHub Actions: https://Github.com/features/actions Manual CI,即手动管理,感觉跟自己在云主机上处理一个概念。便宜,但得自己管理服务器。最著名的就是 Travis CI: https://travis-ci.org/ 了。 怀着激动的心,每个官方都去逛了一遍,确实都提供了 macOS 的构建环境,nice,der~。而且都有都有免费的配额(收费方式都是卖点卡)。 最后还是选择 GitHub Actions 作为远程 CI 的第一个实验品。截图如下 job GitHub Actions 使用 中文的概念介绍和使用方式可以去看阮一峰的 GitHub Actions 入门教程 。 或者直接去看官方文档 GitHub Actions 。 简单来说,就是在仓库目录 .github/workflows/ 放置脚本,当有 push 或 PR 的时候,就会触发脚本运行。 免费额度查看方式,「右上角LOGO」-「Settings」-「Billings & plans」 触发条件 name: iOS starter workflow # 触发条件,在 main 分支上有 push 和 PR 行为时触发脚本 on: push: branches: [ main ] pull_request: branches: [ main ] Step 整理 构建环境 jobs: build: name: Build and Test default scheme using any available iPhone simulator # 环境 runs-on: macos-latest steps: # Checkout 当前分支进行处理 - name: Checkout uses: actions/checkout@v2 - name: stepxxx # operation Pod install iOS 项目免不了用 cocoaPods 安装依赖。 - name: Install Dependencies run: | pod install --repo-update 踩坑记录 在 Podfile 头部标记 source 'https://github.com/CocoaPods/Specs.git' 不是很明智。 cocoaPods 官方在 CocoaPods 1.7.2 — Master Repo CDN is Finalized! 表示,随着 pod 数目的增多,利用 github 作为数据库备份越来越慢了,已经更换成 CDN 的形式了。 # source 'https://github.com/CocoaPods/Specs.git' # 替换成 CDN,或者干脆删除。因为默认 CDN 源了。 source 'https://cdn.cocoapods.org/' 默认 Scheme - name: Set Default Scheme run: | scheme_list=$(xcodebuild -list -json | tr -d "\n") default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") echo $default | cat >default echo Using default scheme: $default Build 用模拟器进行编译。优先跑 .xcworkspace,无则跑 .xcodeproj。 我测试的时候,在 GitHub Actions 上 xcrun 得到的模拟器名称不正确,所以直接固定了使用 iPhone 11 作为模拟器。 - name: Build env: scheme: ${{ 'default' }} platform: ${{ 'iOS Simulator' }} run: | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) # device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}'` device='iPhone 11' if [ $scheme = default ]; then scheme=$(cat default); fi if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` xcodebuild build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" Test - name: Test env: scheme: ${{ 'default' }} platform: ${{ 'iOS Simulator' }} run: | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) # device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}'` device='iPhone 11' if [ $scheme = default ]; then scheme=$(cat default); fi if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` xcodebuild test-without-building -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" 通知以及跨Job传递状态 使用 ravsamhq/notify-slack-action@v1 将 Job 状态通知到 Slack。 因为该 action 运行在 Linux 系统,所以需要另起一个 Job。 如何将上一个 Job 的状态通知到下一个 Job?一种思路是将 Job_A的状态通过文本保存到本地,再在Job_B中读取该状态。 可参考:How get the status of previous jobs 保存状态 - name: Create file status_build_test.txt and write the job status into it if: always() run: | echo ${{ job.status }} > status_build_test.txt - name: Upload file status_build_test.txt as an artifact if: always() uses: actions/upload-artifact@v1 with: name: pass_status_build_test path: status_build_test.txt 读取状态 - name: Download artifact pass_status_build_test uses: actions/download-artifact@v1 with: name: pass_status_build_test - name: Set the statuses of Job build output parameters id: set_outputs run: | echo "::set-output name=status_build_test::$(<pass_status_build_test/status_build_test.txt)" 附录-完整版 附上我当前使用的完整脚本,持续完善中。 name: iOS starter workflow on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: name: Build and Test default scheme using any available iPhone simulator runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v2 - name: Install Dependencies run: | pod install --repo-update - name: Set Default Scheme run: | scheme_list=$(xcodebuild -list -json | tr -d "\n") default=$(echo $scheme_list | ruby -e "require 'json'; puts JSON.parse(STDIN.gets)['project']['targets'][0]") echo $default | cat >default echo Using default scheme: $default - name: Build env: scheme: ${{ 'default' }} platform: ${{ 'iOS Simulator' }} run: | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) # device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}'` # 直接指定 iPhone 11 device='iPhone 11' if [ $scheme = default ]; then scheme=$(cat default); fi if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` xcodebuild build-for-testing -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" - name: Test env: scheme: ${{ 'default' }} platform: ${{ 'iOS Simulator' }} run: | # xcrun xctrace returns via stderr, not the expected stdout (see https://developer.apple.com/forums/thread/663959) # device=`xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | awk '{$1=$1;print}'` # 直接指定 iPhone 11 device='iPhone 11' if [ $scheme = default ]; then scheme=$(cat default); fi if [ "`ls -A | grep -i \\.xcworkspace\$`" ]; then filetype_parameter="workspace" && file_to_build="`ls -A | grep -i \\.xcworkspace\$`"; else filetype_parameter="project" && file_to_build="`ls -A | grep -i \\.xcodeproj\$`"; fi file_to_build=`echo $file_to_build | awk '{$1=$1;print}'` xcodebuild test-without-building -scheme "$scheme" -"$filetype_parameter" "$file_to_build" -destination "platform=$platform,name=$device" - name: Create file status_build_test.txt and write the job status into it if: always() run: | echo ${{ job.status }} > status_build_test.txt - name: Upload file status_build_test.txt as an artifact if: always() uses: actions/upload-artifact@v1 with: name: pass_status_build_test path: status_build_test.txt # 因为 notify-slack-action 跑在 Linux 系统所以另起一个 Job notify: needs: [ build ] if: always() name: Notify Slack runs-on: ubuntu-latest steps: - name: Download artifact pass_status_build_test uses: actions/download-artifact@v1 with: name: pass_status_build_test - name: Set the statuses of Job build output parameters id: set_outputs run: | echo "::set-output name=status_build_test::$(<pass_status_build_test/status_build_test.txt)" - name: Notify Slack uses: ravsamhq/notify-slack-action@v1 if: always() with: status: ${{ steps.set_outputs.outputs.status_build_test }} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} ## Publication Information - [Untitled](https://paragraph.com/@0x74cdebd6028e5d8407c2ad6f13bedd9892e3fb2a/): Publication homepage - [All Posts](https://paragraph.com/@0x74cdebd6028e5d8407c2ad6f13bedd9892e3fb2a/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@0x74cdebd6028e5d8407c2ad6f13bedd9892e3fb2a): Subscribe to updates