ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Xcode Cloudワークフローの拡張
Xcode Cloudをそれぞれの開発ニーズに適応させる方法を確認しましょう。開始条件、カスタムエイリアス、カスタムスクリプト、Webhook、App Store Connect APIを使用してテストと配信を自動化し、ワークフローを効率化する方法をご紹介します。
関連する章
- 0:00 - Introduction
- 1:08 - Essential Workflow Concepts
- 3:00 - Scale your workflows
- 11:38 - Connect other systems
- 12:33 - App Store Connect API
- 16:35 - Webhooks
- 20:33 - Wrap up
リソース
- Configuring start conditions
- Configuring webhooks in Xcode Cloud
- Environment variable reference
- Forum: Developer Tools & Services
- Sharing build configurations across Xcode Cloud workflows
- Writing custom build scripts
関連ビデオ
WWDC23
WWDC21
-
ダウンロード
こんにちは Danielです 後半ではColinにも参加してもらいます ここでは Xcode Cloudのパワフルで 拡張性に優れた機能について説明します これらの機能はワークフローの スケーリングと拡張に役立ちます Xcode CloudはXcodeに組み込まれた 継続的インテグレーションおよび デリバリサービスであり App Store Connectでも利用できます Appleデベロッパ向けに設計されたもので 高品質なアプリの開発と提供を加速させます アプリのビルド 並列での自動テストの実行 テスターへのアプリの配信 優れたアプリの開発に役立つ ユーザーフィードバックの 確認と管理をサポートする 様々な クラウドベースのツールが用意されています
このセッションでは Xcode Cloudワークフローの 拡張方法について説明します まず ワークフローの基本的な概念を 確認します 次に アプリの成長に応じてワークフローの スケーリングを行う方法を紹介します 最後に Xcode Cloud以外のシステムと 連携できるようにワークフローを拡張します
では ワークフローの基本的な概念を 確認することから始めましょう 全てのApple Developer Programメンバーシップは 月25時間のビルド時間を提供します このセッションでは Xcode Cloudを初めて使用する方も ワークフローの柔軟性を高めたい方も 有益な情報が得られます
先ほど触れたように Xcode Cloudには皆さんの開発作業に 役立つ様々な機能がありますが すべての出発点は シンプルなワークフローです ワークフローは4つの要素で構成されます 環境 開始条件 ビルドアクション ポストアクションです
環境では ワークフローの 環境変数を定義します ここではワークフローの 実行時に使用するXcodeと macOSのバージョンも指定します 特定のバージョンを選択することも 最新リリースなどの エイリアスから選ぶこともできます
開始条件では ワークフローを 実行するタイミングを指定します ブランチ プルリクエスト Gitタグのアップデートなど ソースコントロールイベントへの 対応を選択できます スケジュールを設定して 特定の日時にワークフローを 実行することもできます または 手動開始条件を選択すると 手動でのみ実行することができます
ビルドアクションでは ソースコードに対して Xcode Cloudで何を行うかを記述します ワークフローに1つ以上の アクションを設定できます アプリのビルド
テストの実行 分析 またはアーカイブを選択し アプリの配信に向けて準備できます 最後に ポストアクションでは ビルドアクションの 実行完了後の処理を定義します ビルド完了時にSlackで チームに通知する場合や アーカイブ済みアプリを公証したり TestFlightへ配信したりする場合などが 考えられます
大抵の場合はシンプルな ワークフローだけで多くのことができます しかし ワークフローはパワフルです アプリの成長に応じて ニーズに合わせたスケーリングが可能です 私たちは最近 開発中のアプリに機能を追加して サービスからデータを 取得できるようにしました 既存のXcode Cloudワークフローは コードがメインブランチに プッシュされた時に開始されます 最新の変更をビルドした後 テストを実行し 最後に TestFlightのテスターに 変更が送信されます
このテストスイートはユニットとUIのテストで 構成され モックデータを使用します アプリのUIとロジックを実際に 試すのに適しています しかし このアプリには サービスへの依存関係があるため テストサーバとやり取りする 統合テストをいくつか追加しました
統合テストは 依存関係を持つアプリが 全体として正常に機能することを 確認するのに役立ちます ただし この統合テストを実行するには より多くの時間とリソースが必要になり ネットワーク状態など アプリで制御できない 問題の影響を受ける可能性があります 統合テストを実行するワークフローを 設定できるようにしたいのですが サーバに依存し 実際のネットワークリクエストを行うため このワークフローを実行するタイミングを より細かく制御できると便利です Xcode 15.1の新機能である 手動開始条件を使用すると ワークフローを 手動で開始するように設定できます その方法を見てみましょう まず 新しいテストプランを実行するだけの 新しいワークフローを作成します
時間を節約するため 既存のワークフローを複製します
ワークフローを表示するには Cloudのレポートナビゲータで アプリを副ボタンでクリックし を選択します
ビューで 既存のワークフローを副ボタンでクリックし アクションを選択します ワークフローエディタに 新しいワークフローが表示されます このワークフローの名前を 「Integration Tests」に変更し は空白のままにします 次に 条件として追加し 既存の条件を削除します
これでこのワークフローは 手動で指定した時だけ開始され 他のイベントでは開始されません デフォルトのオプションを そのまま使用すると 開始条件がすべての ブランチと関連付けられます ただし ブランチ PR またはGitタグを 選択して この開始条件を 関連付けるGit参照を 具体的に指定することもできます
アクションを変更して IntegrationTestsテストプランを
現時点では このワークフローの終了時に 何も実行する必要がないため ポストアクションを削除して をクリックします
もちろん タブで指定された 様々なバージョンのXcodeや macOSを使用して ワークフローを実行するよう設定できます ただし 今回はどちらのワークフローも 必ず同じバージョンを使用して 実行する必要があります
カスタムエイリアスは Xcode 15.3の新機能です Xcode Cloudの現行リリースにも 既にいくつかエイリアスがあり 利用可能な最新バージョンで ワークフローを実行するように指定できます さらに今回は チーム独自のエイリアスを 定義できるようになりました
1つ以上のワークフローでエイリアスを 使用すると それらのワークフローは そのエイリアスで指定されたバージョンの XcodeまたはmacOSで実行されます つまり エイリアスを更新した場合 そのエイリアスを 使用しているすべてのワークフローが 更新後の値で実行されます 先ほど作成した新しいワークフローで この機能を使ってみましょう
特定のアプリのカスタムエイリアスを すべて表示するには ナビゲータで アプリを副ボタンでクリックし を選択します
新しいXcodeエイリアスを作成するため ボタンをクリックします カスタムエイリアスエディタが 表示されます ここにカスタムエイリアスの名前を入力し それに対応させるXcodeの バージョンを選択します ここでは「Team Preference」と名付け Xcode 15.3を選択します
をクリックし macOSの エイリアスについても同様に設定します
このエイリアスを ワークフローで使用するには ビューに戻って ワークフローを開き タブを表示します
Xcodeバージョンのドロップダウンに 作成したカスタムエイリアスが表示されます これを選択し macOSについても同様にします
カスタムエイリアスの作成と管理は この画面のバージョン選択 ドロップダウン または メニューのからでも 簡単に行えます 最後に 最初のワークフローに戻り 新しいエイリアスで使用する XcodeとmacOSのバージョンを更新します
できました これでエイリアスの バージョンを新しく更新するたびに 両方のワークフローに その変更が適用されます
カスタムエイリアスの使い方については こちらのドキュメントをご覧ください
この統合テストはXcode Cloudで機能し 変更の必要はありませんが テストサーバがオンラインの場合にのみ 実行されるようにすると さらにスマートになります それには Xcode Cloudの もう1つの優れた機能を活用して プロジェクトに カスタムスクリプトを追加します
リポジトリ内で カスタムスクリプトを定義して ビルドの特定のタイミングで 実行することができます
リポジトリのクローンの作成後 xcodebuildの実行前または 実行後のいずれかのタイミングです
カスタムスクリプトの詳細については 「Customize your advanced Xcode Cloud workflows」をご覧ください
ワークフローで定義されている すべての環境変数に加え Xcode Cloudで用意されている環境変数も スクリプトで使用できます 今日はそのうちの2つを カスタムスクリプトで使用します
使用可能な環境変数の一覧は ドキュメントの「Environment variable reference」ページをご覧ください
Xcode Cloudでは すべてのカスタムスクリプトを プロジェクトのルートのci_scriptsフォルダに 格納する必要があります
スクリプトのファイル名によって ビルドのどのタイミングで 実行されるかが決まります
このシナリオでは テストの実行直前に スクリプトを実行します
Xcodeのプロジェクトナビゲータで プロジェクトを副ボタンでクリックして スクリプトのフォルダを作成します
このフォルダに 新しいスクリプトファイルを追加し ci_pre_xcodebuild.shという名前を付けて テストの前に実行されるようにします
統合テストのためのアクションのみを 実行したいので ビルドアクションとworkflowIDの 環境変数をチェックする ロジックを追加します
ここでスクリプトチェックを行い テストのビルトアクションを実行すること ワークフローとIDが 一致することを確認します ワークフローのIDを取得するには Cloudのレポートナビゲータに移動して ワークフローを副ボタンでクリックし を選択します
IDをコピーしたら スクリプトに戻ってペーストします
このブロック内で curlを使用してサーバの 健全性チェックエンドポイントを呼び出し エラー発生時には 詳細なエラーログを出力します
set -e shellオプションを指定すると エラーの発生時に このスクリプトはただちに終了し ワークフローはそこで停止します サーバに到達不能な場合は このような対応が必要です
Xcode Cloudのビルドは 一時的なタスクワーカーで実行されるため ホストが使用する IPアドレスの範囲は様々です Xcode Cloudとサーバが 通信できるようにするため ここでは 必要なIPアドレス範囲を サーバのファイアウォールの インバウンド許可リストに追加しています
許可するIPアドレスの詳細については ドキュメントの「Requirements for using Xcode Cloud」ページをご覧ください ここでは手動開始条件を使用して 統合テスト用の 新しいワークフローを構築しました また カスタムエイリアスを使用し すべてのワークフローでmacOSと Xcodeのバージョンを一致させました さらに カスタムスクリプトを活用して テストを少しスマートにし テストサーバに到達不能な場合は 早期にエラーになるようにしました Xcode Cloudと他のシステムを連携させて ワークフローをさらに強化する 方法については Colinに説明してもらいます
Daniel ありがとう ワークフローと そのスケーリング方法について 理解したところで 次は Xcode Cloud以外のシステムと 接続する方法を見ていきましょう
Danielが説明したように このアプリはサービスに依存しています この新しい依存関係にはリスクが伴います 誰かがコード変更をサーバにプッシュすると アプリに問題が生じるためです
このリスクを軽減するには 新しいIntegration Tests ワークフローを実行します このワークフローで生成される テスト結果により アプリが最新のサーバ変更に 対応しているかどうか確認できます 現時点では このワークフローは 手動でのみ実行されますが App Store Connect APIを使用すると テストサーバが変更されるたびに ビルドを自動的に開始できます
App Store Connect APIにより Xcode Cloudに関連するタスクを含め App Store Connecの 様々なタスクを自動化できます これらのエンドポイントの詳細と その呼び出し方法については ドキュメントをご覧ください
App Store Connect APIを使用して Integration Testsワークフローを実行するには 何が必要かを逆算で考えてみましょう
CiBuildRunsエンドポイントにより 新しいXcode Cloudのビルドを作成できます
この呼び出しでは 実行するワークフローの識別子と ビルドするgitReferenceを 指定する必要があります Integration Testsワークフローの IDはわかっていますが ビルドするブランチの gitReferenceofはわかりません これを確認するには ScmRepositoriesエンドポイントを使用します repositoryIDを使用して このエンドポイントを呼び出すことで そのリポジトリに関連付けられている すべてのブランチ タグ プルリクエストを 取得できます しかし まずワークフローで使用されている repositoryIDを知る必要があります これはCiWorkflowsリソースに含まれており workflowIDを使って照会できます
このスクリプトでは 合計3つのAPI呼び出しを 行って必要なビルドを作成します
OpenAPIを使用して App Store Connect APIを指定し コードジェネレータを使って 各エンドポイントの厳密に型指定された Swiftコードを作成できるようにします このデモでは 実装に重点を置いています ここで使用するオープンソースの コードジェネレーターの詳細については 「Meet Swift OpenAPI Generator」 をご覧ください ビルドを作成するには 3つのAPI呼び出しが必要になるため 便宜上 生成された APIクライアントで拡張を記述します
ここで必要なのは workflowIDを引数に取り 関連付けられた CiWorkflowsリソースを取得し repositoryIDを返す関数です
次に必要なのは このrepositoryIDを ビルドするgitReferenceIDに 変換する方法です これを行うには SCMRepositories エンドポイントを使用しますが それには リポジトリに関連付けられた すべてのgitReferencesをフェッチして 指定した名前を持つブランチの gitReferenceIDを返します
最後に 新しいビルドを開始します そのために必要なのは workflowIDとgitReferenceIDで これらを ciBuildRunsエンドポイントに渡します
それでは 改めて見ていきましょう まず repoID関数を呼び出しますが ここでIntegration Testsワークフローの workflowIDを指定します
次に ビルドするブランチの名前を指定して branchIDを呼び出します ここでは「main」を使用します
最後に workflowIDとgitReferenceIDを 使用してstartBuildを呼び出して スクリプトは完成です
ただし App Store Connect APIで 開始されるビルドは手動と見なされるため ビルドする参照に対応する手動開始条件を 設定しておく必要があります 今回は Danielが既に あらゆるブランチに対応する 手動開始条件を このワークフローに設定しているので 問題はありません
これでスクリプトが完成し 新しいサーバコードがプッシュされるたびに テスト結果を生成できるようになりました ここで終了でもよいのですが テスト結果に少し手を加えましょう
ここまでの成果を プロダクション環境に接続して テスト環境での検証後に サーバの変更がデプロイされる ようにしたいと思います
テスト結果を手作業でチェックし プロダクション環境に手動で デプロイすることもできますが もっと良い方法があります
Xcode CloudのWebhook機能を利用すると テスト結果を デプロイメントサービスに接続し テストに合格したサーバの変更を自動的に プロダクション環境にプッシュできます
Webhookにより ビルドイベントの発生時に サービスを対応させることができます そのため 開発プロセスで 利用している他のサービスやツールに Xcode Cloudを統合できます Webhookの詳細と 設定方法については ドキュメントをご覧ください
Webhookを検知して対応する必要があるのは HTTPサーバのみです Swift on Serverを使用すると シンプルな プロジェクトを数ステップで作成できます それにはVaporまたは Hummingbirdフレームワークを使用します このデモでは 実装に焦点を当てるため Webhookを受信できるエンドポイントを持つ Vaporサーバが 既にセットアップされています Webhookを設定すると Xcode Cloudはエンドポイントに リクエストを送信します これは 様々なビルドイベントに関する 詳細情報を含むJSONペイロードで構成されます
ここでは WebhookPayload構造体を定義し 対象となるフィールドを含めています このシナリオで必要なのは ワークフローID ビルドID ビルドの実行状況 ビルドの完了状態です
この構造体を使用して Webhookリクエストをデコードすることで 簡単にペイロードからフィールドを 抽出できます
次に ペイロードのワークフローIDと Integration Testsワークフローの ワークフローIDを比較するための ロジックを追加します
類似したフィールドが2つあります これらはビルドイベントの状態を 示しています ExecutionProgressは ビルドが実行中か 完了したかを示します 一方 completionStatusは ビルドが成功したかどうかを示します if文を更新して 両方のチェックを行い ビルドが正常に 完了していることを確認します
この文のボディでは 統合テストに合格したことを デプロイメントサービスに通知し buildIDを送信します このデプロイメントサービスでは 他のチェックと共にこの情報を使用することで サーバの変更を承認して プロダクション環境にデプロイします
最後に ビルドイベントを Webhookに送信するよう Xcode Cloudに指示します これは App Store Connectの Xcode Cloudビューから行うことができます
アプリを選択した状態でを開き タブをクリックします
新しいWebhookを設定するため ボタンをクリックします
名前を入力し リスナーのURLを追加します
Webhookを設定したら Xcode Cloudを介して サーバの変更を接続するための 準備は完了です ここまでの作業を振り返りましょう
新しいコード変更がサービスの テスト環境にデプロイされるたびに App Store Connect APIを呼び出して Integration Testsワークフローの ビルドを開始します これにより アプリとテストサーバの連携を 検証するテストが実行されます
結果はWebhookリスナーによって処理され すべてのテストに合格すると 変更がプロダクション環境に デプロイされます
すべてが合格の場合 コードのプッシュから プロダクション環境へのデプロイまで パイプラインが完全に自動化されます
しかし アプリで問題が生じるような サーバ変更がプッシュされた場合は
統合テストに不合格となり Webhookリスナーによって プロダクション環境への 変更のデプロイが中止されます これが私たちの求める動作です
ここで取り上げているのは サーバのコード変更なので 変更をプッシュしたチームメンバーが 自分のマシンにXcodeを インストールしていない可能性があります しかし Xcode Cloudは App Store Connectに組み込まれているため その場合でも 各自のブラウザで 問題の確認と調査を行えます
こちらは想定どおりに 実行できなかった場合の App Store Connectのビルドの結果です セクションを見ると IntegrationTestsアクションに 問題があったことがわかります
セクションをクリックすると テストレポートが開きます
App Store Connectからでも Xcodeを開いているかのように 確認などの操作を行えます テストステータスでフィルタリングしたり テストスイートで検索したり 各テストの詳細情報を表示したりできます ここでは サーバから返されたデータに 問題があったことがわかります
さらに詳しい調査が必要な場合は サイドバーのをクリックして Xcodeのログやクラッシュレポートなど ビルドに関連する 様々なファイルをダウンロードできます このセッションでは様々なことを 取り上げましたが Xcode Cloudでワークフローを 拡張する方法のほんの一部に過ぎません
ここでは手動開始条件 カスタムエイリアス カスタムスクリプトを使用して チームのワークフローを拡張し App Store Connect APIと Webhookを使って 他のシステムに統合しました
「Create practical workflows in Xcode Cloud」や 「Meet Xcode Cloud」など 他のセッションもご覧ください Xcode CloudでチームのCIプロセスを 強化する方法について説明しています
これらのコンセプトを実際のワークフローに 取り入れる方法について 何か新しいヒントを 得ていただけたらと思います ありがとうございました
-
-
10:02 - Custom Script
#!/bin/sh set -e if [[ $CI_XCODEBUILD_ACTION == "test-without-building" && $CI_WORKFLOW_ID == "82D89C93-B69C-46B5-A794-A2BCFD3EE487" ]] then curl https://example.com/health --fail fi
-
14:01 - App Store Connect API - Client Extension
extension Client { func repoID(workflowID: String) async throws -> String { return try await ciWorkflowsGetInstance( path: .init(id: workflowID), query: .init(include: [.repository]) ).ok.body.json.data.relationships!.repository!.data!.id } func branchID(repoID: String, name: String) async throws -> String { return try await scmRepositoriesGitReferencesGetToManyRelated( path: .init(id: repoID) ) .ok.body.json.data .filter { $0.attributes!.kind == .BRANCH && $0.attributes!.name == name } .first!.id } func startBuild(workflowID: String, gitReferenceID: String) async throws { _ = try await ciBuildRunsCreateInstance( body: .json(.init( data: .init( _type: .ciBuildRuns, relationships: .init( workflow: .init(data: .init( _type: .ciWorkflows, id: workflowID )), sourceBranchOrTag: .init(data: .init( _type: .scmGitReferences, id: gitReferenceID )) ) ) )) ).created } }
-
14:43 - App Store Connect API - Main Function
static func main() async throws { let client = try Client( serverURL: Servers.server1(), configuration: .init(dateTranscoder: .iso8601WithFractionalSeconds), transport: URLSessionTransport(), middlewares: [AuthMiddleware(token: ProcessInfo.processInfo.environment["TOKEN"]!)] ) let workflowID = "82D89C93-B69C-46B5-A794-A2BCFD3EE487" let repoID = try await client.repoID(workflowID: workflowID) let branchName = "main" let branchID = try await client.branchID(repoID: repoID, name: branchName) try await client.startBuild(workflowID: workflowID, gitReferenceID: branchID) }
-
17:09 - Webhook Handler Implementation
struct WebhookPayload: Content { let ciWorkflow: CiWorkflow let ciBuildRun: CiBuildRun struct CiWorkflow: Content { let id: String } struct CiBuildRun: Content { let id: String let executionProgress: String let completionStatus: String } } func routes(_ app: Application) throws { let deploymentService = ExampleDeploymentClient() let workflowID = "82D89C93-B69C-46B5-A794-A2BCFD3EE487" app.post("webhook") { req async throws -> HTTPStatus in return HTTPStatus.ok } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。