iOS 開発用 CI システム: Intel から ARM への変換
公開: 2024-02-14進化し続けるテクノロジーの状況において、企業は変化の風に適応して競争力を維持する必要があります。 テクノロジーの世界を席巻したそのような変革の 1 つは、Apple の画期的な Apple M1 チップに代表される、Intel x86_64 アーキテクチャから iOS ARM アーキテクチャへの移行です。 これに関連して、 iOS 用 CI システムは、この変化を乗り越える企業にとって重要な考慮事項となっており、ソフトウェア開発とテストのプロセスが効率的かつ最新の技術標準に準拠した状態に保たれるようにします。
Apple は約 3 年前に M1 チップを発表し、それ以来、同社が ARM アーキテクチャを採用し、最終的には Intel ベースのソフトウェアのサポートを終了することは明らかでした。 アーキテクチャ間の互換性を維持するために、Apple は、独自のバイナリ変換フレームワークである Rosetta の新バージョンを導入しました。これは、2006 年の PowerPC から Intel への大幅なアーキテクチャ変革の際に、過去に信頼性が証明されています。変革はまだ進行中です。 Xcode はバージョン 14.3 で Rosetta のサポートを失います。
Miquido では、数年前に Intel から ARM に移行する必要性を認識しました。 私たちは 2021 年半ばに準備を開始しました。複数のクライアント、アプリ、プロジェクトを同時に進行しているソフトウェア ハウスとして、私たちは克服しなければならないいくつかの課題に直面しました。 この記事は、あなたの会社が同様の状況に直面した場合のハウツーガイドとして役立ちます。 説明されている状況と解決策は iOS 開発の観点から描かれていますが、他のテクノロジーにも適した洞察が見つかるかもしれません。当社の移行戦略の重要な要素には、iOS 用 CI システムが新しいアーキテクチャに合わせて完全に最適化されていることを確認することが含まれており、以下の重要性を強調しています。このような大きな変化の中でも、効率的なワークフローと高品質の出力を維持するiOS 用 CI システム。
問題: Intel から iOS ARM アーキテクチャへの移行
移行プロセスは 2 つの主要な分岐に分かれていました。
1. 既存の Intel ベースの開発者のコンピューターを新しい M1 Macbook に置き換える
このプロセスは比較的単純であるはずでした。 私たちは、開発者のすべての Intel Macbook を 2 年間かけて段階的に置き換える方針を確立しました。 現在、従業員の 95% が ARM ベースの Macbook を使用しています。
ただし、このプロセス中に予期せぬ課題がいくつか発生しました。 2021 年半ば、M1 Mac の不足により、交換プロセスが遅れました。 2021 年末までに、約 200 台の待機中の Macbook のうち数台しか交換できませんでした。 iOS 以外のエンジニアも含め、同社のすべての Intel Mac を M1 Macbook に完全に置き換えるには約 2 年かかると推定しました。
幸いなことに、Apple は新しい M1 Pro および M2 チップをリリースしました。 その結果、インテルを M1 Mac に置き換えることから、M1 Pro および M2 チップに置き換えることに焦点を移しました。
ソフトウェアがスイッチに対応できないことが開発者の不満の原因となった
新しい M1 Macbook を最初に受け取ったエンジニアは、ほとんどのソフトウェアが Apple の新しい iOS ARM アーキテクチャに切り替える準備ができていなかったため、苦労しました。 Rubygems や Cocoapods などのサードパーティ ツールは、他の多くの Rubygems に依存する依存関係管理ツールであり、最も影響を受けました。 これらのツールの一部は当時、iOS ARM アーキテクチャ用にコンパイルされていなかったため、ほとんどのソフトウェアは Rosetta を使用して実行する必要があり、パフォーマンスの問題やフラストレーションが発生していました。
しかし、ソフトウェアの作成者は、これらの問題のほとんどが発生すると解決するよう努めました。 画期的な瞬間は、Rosetta のサポートが終了した Xcode 14.3 のリリースで起こりました。 これは、Apple が Intel から iOS への ARM アーキテクチャへの移行を推進していることをすべてのソフトウェア開発者に明確に示すものでした。 これにより、これまで Rosetta に依存していたほとんどのサードパーティ ソフトウェア開発者は、ソフトウェアを ARM に移行する必要がありました。 現在、Miquido で日常的に使用されているサードパーティ ソフトウェアの 99% は、Rosetta なしで実行されます。
2. Miquido の iOS 向け CI システムの置き換え
Miquido の継続的統合 iOS システムの置き換えは、単にマシンを交換するよりも複雑な作業であることが判明しました。まず、当時の当社のインフラストラクチャをご覧ください。
Gitlab クラウド インスタンスとそれに接続した 9 台の Intel ベースの Mac Mini がありました。 これらのマシンはジョブ ランナーとして機能し、Gitlab がオーケストレーションを担当しました。 CI ジョブがキューに入れられると、Gitlab はそのジョブを、gitlab-ci.yml ファイルで指定されたプロジェクト要件を満たす最初の利用可能なランナーに割り当てました。 Gitlab は、すべてのビルド コマンド、変数、パスなどを含むジョブ スクリプトを作成します。その後、このスクリプトはランナーに移動され、そのマシン上で実行されます。
このセットアップは堅牢に見えるかもしれませんが、Intel プロセッサのサポートが不十分なため、仮想化で問題に直面しました。 その結果、Dockerなどの仮想化は利用せず、物理マシン自体でジョブを実行することにしました。 Docker に基づいた効率的で信頼性の高いソリューションをセットアップしようとしましたが、GPU アクセラレーションの欠如などの仮想化の制限により、ジョブの実行に物理マシンでの実行に比べて 2 倍の時間がかかりました。 これにより、オーバーヘッドが増加し、キューが急速にいっぱいになりました。
macOS SLA により、同時にセットアップできる VM は 2 つだけでした。 したがって、物理ランナーのプールを拡張し、オペレーティング システム上で Gitlab ジョブを直接実行するようにセットアップすることにしました。 ただし、このアプローチにはいくつかの欠点もありました。
ビルドプロセスとランナー管理における課題
- ビルド ディレクトリ サンドボックスの外ではビルドが分離されません。
ランナーはすべてのビルドを物理マシン上で実行します。これは、ビルドがビルド ディレクトリ サンドボックスから分離されていないことを意味します。 これには長所と短所があります。 一方で、ほとんどのプロジェクトはサードパーティの依存関係の同じセットを使用するため、システム キャッシュを使用してビルドを高速化できます。
一方、あるプロジェクトの残り物が他のすべてのプロジェクトに影響を与える可能性があるため、キャッシュは維持できなくなります。 Flutter 開発と React Native 開発の両方に同じランナーが使用されるため、これはシステム全体のキャッシュにとって特に重要です。 特に React Native では、NPM を通じてキャッシュされた多くの依存関係が必要です。
- システムツールが混乱する可能性があります。
どちらのジョブも sudo 権限で実行されていませんでしたが、それでも一部のシステムまたはユーザー ツール (Ruby など) にアクセスすることは可能でした。 これにより、特に macOS が一部のレガシー Xcode 機能を含むレガシー ソフトウェアの一部に Ruby を使用しているため、これらのツールの一部が壊れるという潜在的な脅威が生じました。 Ruby のシステム バージョンは、いじる必要はありません。
ただし、rbenv を導入すると、処理がさらに複雑になります。 Rubygem は Ruby のバージョンごとにインストールされ、これらの gem の一部には特定のバージョンの Ruby が必要であることに注意することが重要です。 私たちが使用していたサードパーティ ツールのほとんどは Ruby に依存しており、Cocoapods と Fastlane が主役でした。
- 署名 ID の管理。
ランナー上のシステム キーチェーンに関しては、さまざまなクライアント開発アカウントからの複数の署名 ID を管理することが課題になる場合があります。 署名 ID は非常に機密性の高いデータであり、これによりアプリケーションの共同設計が可能になり、潜在的な脅威に対して脆弱になります。
セキュリティを確保するには、プロジェクト全体で ID をサンドボックス化し、保護する必要があります。 ただし、macOS のキーチェーン実装で複雑さが増すことを考慮すると、このプロセスは悪夢になる可能性があります。
- マルチプロジェクト環境における課題。
すべてのプロジェクトが同じツール、特に Xcode を使用して作成されたわけではありません。 一部のプロジェクト、特にサポート段階にあるプロジェクトは、プロジェクトの開発に使用した最後のバージョンの Xcode を使用して維持されました。 これは、これらのプロジェクトで何らかの作業が必要な場合、CI がそれを構築できる必要があることを意味します。 その結果、ランナーは複数のバージョンの Xcode を同時にサポートする必要があり、特定のジョブに使用できるランナーの数が事実上絞り込まれていました。
5. 余分な努力が必要です。
ソフトウェアのインストールなど、ランナー全体で行われた変更は、すべてのランナーで同時に実行する必要があります。 このための自動化ツールはありましたが、自動化スクリプトを維持するために余分な労力が必要でした。
顧客の多様なニーズに合わせてカスタマイズされたインフラストラクチャ ソリューション
Miquido は、さまざまなニーズを持つ複数のクライアントと連携するソフトウェア ハウスです。 私たちは、各クライアントの特定の要件を満たすようにサービスをカスタマイズします。 中小企業や新興企業には、それを維持するためのリソースや知識が不足している可能性があるため、私たちはコードベースと必要なインフラストラクチャをホストすることがよくあります。
企業クライアントは通常、プロジェクトをホストするための独自のインフラストラクチャを持っています。 ただし、その能力がない企業や、業界規制によりインフラストラクチャを使用することが義務付けられている企業もあります。 また、Xcode Cloud や Codemagic などのサードパーティの SaaS サービスも使用したくないと考えています。 代わりに、彼らは既存のアーキテクチャに適合するソリューションを求めています。
これらのクライアントに対応するために、私たちは多くの場合、自社のインフラストラクチャ上でプロジェクトをホストするか、クライアントのインフラストラクチャ上に同じ継続的統合 iOS 構成をセットアップします。 ただし、ID への署名など、機密情報やファイルを扱う場合には細心の注意を払います。
Fastlane を活用して効率的なビルド管理を行う
ここで Fastlane が便利なツールとして登場します。 これは、プロセスを合理化し、異なるクライアント間でプロセスを分離するのに役立つアクションと呼ばれるさまざまなモジュールで構成されています。 これらのアクションの 1 つであるmatch は、開発および運用の署名 ID とプロビジョニング プロファイルの維持に役立ちます。 また、OS レベルでも機能して、ビルド時にこれらの ID を個別のキーチェーンに分離し、ビルド後にクリーンアップを実行します。これは、すべてのビルドを物理マシン上で実行しているため、非常に役立ちます。
私たちは当初、特定の理由から Fastlane に目を向けましたが、Fastlane には私たちにとって役立つ追加機能があることがわかりました。
- Testflight へのビルドのアップロード
以前は、AppStoreConnect API は開発者向けに公開されていませんでした。 これは、ビルドを Testflight にアップロードする唯一の方法が Xcode を使用するか、Fastlane を使用することであることを意味しました。 Fastlane は、基本的に ASC API をスクレイピングし、それをパイロットと呼ばれるアクションに変えるツールでした。 ただし、このメソッドは次の Xcode アップデートで機能しなくなることがよくありました。 開発者がコマンド ラインを使用してビルドを Testflight にアップロードしたい場合、 Fastlane が最良の選択肢でした。
- Xcode バージョン間の簡単な切り替え
単一マシン上に複数の Xcode インスタンスがあるため、ビルドに使用する Xcode を選択する必要がありました。 残念ながら、Apple は Xcode バージョン間の切り替えを不便にしました。切り替えには「xcode-select」を使用する必要があり、さらに sudo 権限が必要です。 ファストレーンもそれをカバーしています。
- 開発者向けの追加ユーティリティ
Fastlane は、バージョニングやビルド結果を Webhook に送信する機能など、他にも多くの便利なユーティリティを提供します。
ファストレーンのデメリット
Fastlane を私たちのプロジェクトに適応させることは確実で確実だったので、その方向に進みました。 数年間問題なく使用できました。 しかし、ここ数年の間に、いくつかの問題が判明しました。
- Fastlane には Ruby の知識が必要です。
Fastlane は Ruby で書かれたツールであり、効果的に使用するには Ruby についての十分な知識が必要です。 Fastlane 構成またはツール自体にバグがある場合、 irbまたはpry を使用してバグをデバッグするのは非常に困難な場合があります。
- 多数の gem への依存。
Fastlane 自体は約 70 個の gem に依存しています。 システム Ruby を分割するリスクを軽減するために、プロジェクトはローカル バンドラー gem を使用していました。 これらの gem をすべて取得すると、多くの時間オーバーヘッドが発生します。
- システム Ruby と Rubygems の問題。
その結果、前述したシステム Ruby と Rubygems に関するすべての問題がここにも当てはまります。
- Flutter プロジェクトの冗長性。
Flutter プロジェクトでは、iOS プロジェクトとの互換性を維持し、ランナーのキーチェーンを保護するためだけに、 fastlane matchを使用する必要がありました。 Flutter には独自のビルド システムが組み込まれており、前述のオーバーヘッドは署名 ID とプロビジョニング プロファイルを管理する目的でのみ導入されたため、これは途方もなく不必要でした。
これらの問題のほとんどは途中で修正されましたが、より堅牢で信頼性の高いソリューションが必要でした。
アイデア: 新しい、より堅牢な継続的統合ツールを iOS に適応させる
良いニュースは、Apple がチップ アーキテクチャを完全に制御できるようになり、macOS 用の新しい仮想化フレームワークを開発したことです。 このフレームワークを使用すると、ユーザーは、迅速に起動し、ネイティブのようなパフォーマンスを特徴とする Linux または macOS の仮想マシンを作成、構成、実行できます。これは実際にネイティブのようなことを意味します。
これは有望に思え、iOS 用の新しい継続的統合ツールの基礎となる可能性があります。 しかし、それは完全な解決策のほんの一部にすぎませんでした。 VM 管理ツールがあれば、そのフレームワークを Gitlab ランナーと連携して使用できるものも必要でした。
そうすれば、仮想化パフォーマンスの低下に関する問題のほとんどは解決されるでしょう。 また、Fastlane で解決しようとしていた問題のほとんどを自動的に解決できるようになります。
オンデマンド署名 ID 管理のためのカスタマイズされたソリューションの開発
最後に解決すべき問題が 1 つありました。それは署名 ID 管理です。 Fastlane は私たちのニーズに対して過剰に思えたので、これには使いたくありませんでした。 代わりに、私たちは要件により適したソリューションを探していました。 私たちのニーズは単純でした。ID 管理プロセスは、キーチェーンに ID を事前にインストールせずに、ビルド時のみにオンデマンドで実行し、実行されるあらゆるマシンと互換性を持たせる必要がありました。
配布の問題と安定した AppstoreConnect API の欠如は、Apple がユーザーと ASC 間の通信を可能にする「altool」をリリースしたときに廃止されました。
そこで私たちはアイデアを思いつき、これら 3 つの側面を結び付ける方法を見つける必要がありました。
- Apple の仮想化フレームワークを利用する方法を見つける。
- Gitlab ランナーで動作させる。
- 複数のプロジェクトとランナーにわたる署名 ID 管理のためのソリューションを見つける。
解決策: 当社のアプローチを垣間見る (ツールを含む)
私たちは、前述したすべての問題に対処するための解決策を探し始めました。
- Apple の仮想化フレームワークを利用します。
最初の障害については、非常に早く解決策を見つけました。Cirrus Labs の Tart ツールを見つけました。 最初の瞬間から、これが私たちの選択になるだろうとわかっていました。
Cirrus Lab が提供する Tart ツールを使用する最も大きな利点は次のとおりです。
- 生の .ipsw イメージから VM を作成する可能性。
- Cirrus Labs GitHub ページで利用可能な、事前にパックされたテンプレート (brew や Xcode などのいくつかのユーティリティ ツールがインストールされている) を使用して vms を作成する可能性。
- Tart ツールは、動的イメージ構築のサポートにパッカーを利用します。
- Tart ツールは、Linux と MacOS の両方のイメージをサポートしています。
- このツールは、実際にディスク領域を予約せずにファイルの複製を可能にする、APFS ファイル システムの優れた機能を利用します。 こうすることで、元のイメージ サイズの 3 倍のディスク領域を割り当てる必要がなくなります。 元のイメージ用に十分なディスク領域のみが必要ですが、クローンは元のイメージとの差分の領域のみを占有します。 特に macOS イメージは非常に大きくなる傾向があるため、これは非常に役立ちます。
たとえば、Xcode およびその他のユーティリティがインストールされた macOS Ventura イメージを動作させるには、少なくとも 60GB のディスク容量が必要です。 通常の状況では、イメージ 1 つとそのクローン 2 つで最大 180 GB のディスク容量が必要になりますが、これはかなりの量です。 これはほんの始まりにすぎません。複数の元のイメージを使用したり、単一の VM に複数の Xcode バージョンをインストールしたりする場合は、サイズがさらに大きくなる可能性があります。
- このツールを使用すると、元の VM とクローン作成された VM の IP アドレス管理が可能になり、VM への SSH アクセスが可能になります。
- ホスト マシンと VM の間でディレクトリをクロスマウントする機能。
- このツールはユーザーフレンドリーで、非常にシンプルな CLI を備えています。
VM 管理に利用するという点では、このツールに不足しているものはほとんどありません。 1 つを除いて、ほとんど何もありません。有望ではありますが、オンザフライでイメージを構築するためのパッカー プラグインは非常に時間がかかるため、使用しないことにしました。
タルトを試してみましたが、とてもうまくいきました。 ネイティブライクなパフォーマンスで管理も簡単でした。
タルトの統合に成功し、目覚ましい結果をもたらしたので、私たちは次に他の課題への対処に焦点を当てました。
- タルトと Gitlab ランナーを組み合わせる方法を探しています。
最初の問題を解決した後、タルトと Gitlab ランナーをどのように組み合わせるかという問題に直面しました。
まずは、Gitlab ランナーが実際に何を行うのかを説明します。
この図には、タスクをランナー ホストから VM に割り当てるという追加のパズルを含める必要がありました。 GitLab ジョブは、重要な変数、PATH エントリ、コマンドを保持するシェル スクリプトです。
私たちの目的は、このスクリプトを VM に転送して実行することでした。
しかし、この作業は当初考えていたよりも困難であることが判明しました。
ランナー
Docker や SSH などの標準の Gitlab ランナー エグゼキュータはセットアップが簡単で、構成はほとんどまたはまったく必要ありません。 ただし、構成をより細かく制御する必要があったため、GitLab が提供するカスタム エグゼキュータを検討することにしました。
カスタム エグゼキュータは、各ランナー ステップ (準備、実行、クリーンアップ) がシェル スクリプトの形式で記述されるため、非標準構成に最適なオプションです。 唯一欠けていたのは、必要なタスクを実行でき、ランナー構成スクリプトで実行できるコマンド ライン ツールでした。
現在、まさにそれを行うツールがいくつか利用可能です。たとえば、CirrusLabs Gitlab Tart Executor などです。 このツールはまさに当時私たちが探していたものです。 しかし、それはまだ存在しておらず、調査を行った後も、タスクの達成に役立つツールは見つかりませんでした。
独自のソリューションを書く
完璧な解決策が見つからなかったので、自分たちで解決策を書きました。 結局のところ、私たちはエンジニアです! アイデアは確かだと思われ、必要なツールもすべて揃っていたため、開発を進めました。
私たちは、Swift と、Apple が提供するいくつかのオープンソース ライブラリを使用することを選択しました。コマンド ラインの実行を処理する Swift Argument Parser と、VM との SSH 接続を処理する Swift NIO です。 私たちは開発を開始し、数日で、最終的に MQVMRunner に発展するツールの最初に動作するプロトタイプを入手しました。
大まかに言うと、このツールは次のように機能します。
- (準備ステップ)
- gitlab-ci.yml で提供されている変数 (イメージ名と追加の変数) を読み取ります。
- 要求された VM ベースを選択します
- 要求された VM ベースのクローンを作成します。
- クロスマウントされたディレクトリを設定し、Gitlab ジョブ スクリプトをコピーして、それに必要な権限を設定します。
- クローンを実行し、SSH 接続を確認します。
- 必要に応じて、必要な依存関係 (Xcode バージョンなど) をセットアップします。
- (ステップ実行)
- SSH を介して、準備された VM クローン上のクロスマウントされたディレクトリからスクリプトを実行する Gitlab ジョブを実行します。
- (クリーンアップステップ)
- クローンイメージを削除します。
開発における課題
開発中にいくつかの問題が発生し、期待したほどスムーズに進みませんでした。
- IPアドレス管理。
IP アドレスの管理は、慎重に扱う必要がある重要なタスクです。 プロトタイプでは、SSH 処理は直接ハードコードされた SSH シェル コマンドを使用して実装されました。 ただし、非対話型シェルの場合は、キー認証が推奨されます。 さらに、中断を避けるために、ホストを known_hosts ファイルに追加することをお勧めします。 ただし、仮想マシンの IP アドレスは動的に管理されるため、特定の IP のエントリが 2 倍になり、エラーが発生する可能性があります。 したがって、このような問題を防ぐために、特定のジョブに known_hosts を動的に割り当てる必要があります。
- 純粋な Swift ソリューション。
そのことと、Swift コードでハードコーディングされたシェル コマンドがあまりエレガントではないという事実を考慮すると、専用の Swift ライブラリを使用するのが良いだろうと考え、Swift NIO を使用することにしました。 私たちはいくつかの問題を解決しましたが、同時に次のようないくつかの新しい問題を導入しました。たとえば、コマンドの実行が終了したために SSH チャネルが終了した「後」に、標準出力に配置されたログが転送されることがありました。その後の作業でその出力が実行されると、実行がランダムに失敗していました。
- Xcodeのバージョン選択。
Packer プラグインは時間がかかるため、動的イメージの構築にはオプションではなかったため、複数の Xcode バージョンがプリインストールされた単一の VM ベースを使用することにしました。 私たちは、開発者が gitlab-ci.yml で必要な Xcode バージョンを指定できる方法を見つける必要がありました。そして、どのプロジェクトでも使用できるカスタム変数を考案しました。 次に、MQVMRunner はクローン作成された VM 上で「xcode-select」を実行し、対応する Xcode バージョンをセットアップします。
そして、さらにたくさんの
プロジェクトの移行を合理化し、Mac Studio を使用した iOS ワークフローの継続的統合
私たちはそれを 2 つの新しい Mac Studio にセットアップし、プロジェクトの移行を開始しました。 私たちは、開発者にとって移行プロセスを可能な限り透過的にしたいと考えていました。 完全にシームレスにすることはできませんでしたが、最終的には、gitlab-ci.yml で次のいくつかのことを行うだけで済むようになりました。
- ランナーのタグ: Intel の代わりに Mac Studio を使用すること。
- イメージの名前: 複数のベース VM が必要な場合の将来の互換性のために導入されたオプションのパラメーター。 現時点では、常にデフォルトで、所有している単一のベース VM が使用されます。
- Xcode のバージョン: オプションのパラメータ。 指定しない場合は、利用可能な最新バージョンが使用されます。
このツールは初期のフィードバックが非常に良かったため、オープンソースにすることにしました。 Gitlab Custom Runner と必要なすべてのアクションと変数をセットアップするためのインストール スクリプトを追加しました。 私たちのツールを使用すると、数分で独自の GitLab ランナーをセットアップできます。必要なのは、ジョブが実行されるタルトとベース VM だけです。
iOS の最終的な継続的インテグレーションの構造は次のようになります。
3. 効率的なアイデンティティ管理のためのソリューション
私たちは、クライアントの署名 ID を管理するための効率的なソリューションを見つけるのに苦労してきました。 署名 ID は機密性の高いデータであり、必要以上に安全でない場所に保存すべきではないため、これは特に困難でした。
さらに、プロジェクト間のソリューションを使用せずに、ビルド時にのみこれらの ID をロードしたいと考えていました。 これは、アプリ (またはビルド) サンドボックスの外部から ID にアクセスできないことを意味します。 後者の問題については、VM に移行することですでに解決しています。 ただし、ビルド時のみ署名 ID を保存して VM に読み込む方法を見つける必要がありました。
ファストレーンマッチの問題
当時、私たちはまだ Fastlane マッチを使用していました。これは、暗号化された ID とプロビジョニングを別のリポジトリに保存し、ビルド プロセス中にそれらを別のキーチェーン インスタンスにロードし、ビルド後にそのインスタンスを削除します。
このアプローチは便利に見えますが、いくつかの問題があります。
- Fastlane セットアップ全体が機能する必要があります。
Fastlane は Rubygem であり、最初の章に挙げたすべての問題がここに当てはまります。
- ビルド時のリポジトリのチェックアウト。
私たちは、セットアップ プロセスではなくビルド プロセス中にチェックアウトされる別のリポジトリに ID を保持しました。 これは、プライベートなサードパーティの依存関係を処理する方法と同様に、Gitlab だけでなく特定のランナーに対してもアイデンティティ リポジトリへの個別のアクセスを確立する必要があることを意味しました。
- マッチ以外では管理が難しい。
ID の管理やプロビジョニングに Match を利用している場合、手動介入はほとんど、またはまったく必要ありません。 プロファイルを手動で編集、復号化、暗号化して、後で一致を引き続き機能できるようにするのは、面倒で時間がかかります。 Fastlane を使用してこのプロセスを実行すると、通常、アプリケーション プロビジョニング セットアップが完全に消去され、新しいセットアップが作成されます。
- デバッグが少し難しい。
コード署名に問題がある場合は、最初にそれらをデコードする必要があるため、インストールされたばかりの ID とプロビジョニングの一致を判断することが難しい場合があります。
- セキュリティ上の懸念。
提供された認証情報を使用してアクセスされた開発者アカウントを照合し、開発者に代わって変更を加えます。 Fastlane はオープンソースであるにもかかわらず、セキュリティ上の懸念から一部のクライアントがこれを拒否しました。
- 最後になりましたが、 Matchを排除することで、Fastlane を完全に排除する上での最大の障害が取り除かれることになります。
当初の要件は次のとおりでした。
- ロードでは、安全な場所から、できれば非平文形式で ID に署名し、それをキーチェーンに配置する必要があります。
- その ID には Xcode からアクセスできる必要があります。
- できれば、ID パスワード、キーチェーン名、およびキーチェーン パスワード変数はデバッグ目的で設定可能である必要があります。
Match には必要なものがすべて揃っていましたが、特に独自のビルド システムを備えたクロスプラットフォーム ソリューションの場合、Match を使用するためだけに Fastlane を実装するのはやりすぎのように思えました。 私たちは Match に似たものを望んでいましたが、Ruby の重い負担がなく、持ち運びが可能でした。
独自のソリューションを作成する
そこで私たちは、それを自分たちで書こうと考えました。 これは MQVMRunner で行ったので、ここでも行うことができます。 また、そのために Swift を選択したのは、主に、Apple Security フレームワークを使用して必要な API を多数無料で入手できるためです。
もちろん、それも期待したほどスムーズにはいきませんでした。
- セキュリティフレームワークが整備されている。
最も簡単な戦略は、Fastlane と同じように bash コマンドを呼び出すことでした。 ただし、セキュリティ フレームワークが利用できるようになったので、開発に使用するのがよりエレガントになると考えました。
- 経験不足。
私たちは macOS のセキュリティ フレームワークの経験があまりなく、iOS で慣れ親しんでいるものとは大きく異なることがわかりました。 これは、私たちが macOS の制限を認識していなかった、または iOS と同じように動作すると仮定していた多くの場合に裏目に出ました。それらの仮定のほとんどが間違っていました。
- ひどいドキュメント。
Apple Security フレームワークのドキュメントは、控えめに言っても質素です。 これは OSX の最初のバージョンにまで遡る非常に古い API であり、それ以来更新されていないという印象を受けることがありました。 コードの大部分は文書化されていませんが、ソース コードを読んでそれがどのように機能するかを予測しました。 私たちにとって幸いなことに、それはオープンソースです。
- 代替品のない廃止。
このフレームワークの大部分は非推奨になっています。 Apple は、典型的な「macOS スタイル」キーチェーン (パスワードでアクセスできる複数のキーチェーン) から脱却し、「iOS スタイル」キーチェーン (iCloud 経由で同期された単一のキーチェーン) を実装しようとしています。 そのため、彼らは 2014 年に macOS Yosemite でこの API を非推奨にしましたが、過去 9 年間、それに代わるものは見つかりませんでした。つまり、現時点では、新しい API がまだないため、利用できる唯一の API が非推奨になりました。
署名 ID は、base64 でエンコードされた文字列としてプロジェクトごとの Gitlab 変数に保存できると想定しました。 これは安全で、プロジェクトごとに基づいており、マスクされた変数として設定されている場合は、非平文として読み取ってビルド ログに表示できます。
これで、身元データが得られました。 それをキーチェーンに入れるだけで済みました。 セキュリティ API の使用 数回の試行とセキュリティ フレームワークのドキュメントの精査を経て、後に MQSwiftSign となるもののプロトタイプを準備しました。
macOS セキュリティ システムを学ぶ、しかし道のりは難しい
ツールを開発するには、macOS キーチェーンがどのように動作するかを深く理解する必要がありました。 これには、キーチェーンがアイテム、そのアクセスと権限、キーチェーン データの構造を管理する方法の調査が含まれます。 たとえば、キーチェーンは、オペレーティング システムが ACL セットを無視する唯一の macOS ファイルであることがわかりました。 さらに、特定のキーチェーン項目の ACL は、キーチェーン ファイルに保存されたプレーン テキストの plist であることもわかりました。 その過程でいくつかの課題に直面しましたが、多くのことを学びました。
私たちが直面した重要な課題の 1 つはプロンプトでした。 私たちのツールは主に CI iOS システム上で実行するように設計されていたため、非対話型である必要がありました。 CI ではユーザーにパスワードの確認を求めることができませんでした。
ただし、macOS のセキュリティ システムは適切に設計されているため、ユーザーの明示的な許可がなければ、署名 ID を含む機密情報を編集したり読み取ったりすることはできません。 確認なしでリソースにアクセスするには、アクセスするプログラムがリソースのアクセス制御リストに含まれている必要があります。 これは、システムに付属する Apple プログラムであっても、いかなるプログラムも破損してはいけないという厳格な要件です。 プログラムでキーチェーン エントリの読み取りまたは編集が必要な場合、ユーザーはキーチェーン パスワードを入力してロックを解除し、必要に応じてエントリの ACL に追加する必要があります。
ユーザー権限の課題を克服する
そのため、パスワード プロンプトを使用してユーザーに許可を求めずに、Xcode がキーチェーンによって設定された ID にアクセスする方法を見つける必要がありました。 これを行うには、アイテムのアクセス制御リストを変更できますが、それにはユーザーの許可も必要です。もちろん、許可は必要です。 そうしないと、ACL を持つことの意味そのものが台無しになってしまいます。 私たちはその安全装置をバイパスしようとしてきました。「security set-key-partition-list」コマンドと同じ効果を達成しようとしました。
フレームワークのドキュメントを詳しく調べた結果、ユーザーにパスワードの入力を求めずに ACL を編集できる API は見つかりませんでした。 私たちが見つけた最も近いものは `SecKeychainItemSetAccess` で、毎回 UI プロンプトをトリガーします。 それから私たちはさらに詳しく調べましたが、今回は最高のドキュメント、つまりソース コードそのものに取り組みました。 Appleはそれをどのように実装したのでしょうか?
予想通り、彼らはプライベート API を使用していることが判明しました。 `SecKeychainItemSetAccessWithPassword` というメソッドは基本的に `SecKeychainItemSetAccess` と同じことを行いますが、ユーザーにパスワードの入力を求める代わりに、パスワードが関数の引数として提供されます。 もちろん、プライベート API であるため、ドキュメントには記載されていませんが、Apple には、個人または企業で使用するためのアプリを作成することなど考えられないかのように、そのような API に関するドキュメントが不足しています。 このツールは内部使用のみを目的としていたため、ためらうことなくプライベート API を使用しました。 しなければならない唯一のことは、C メソッドを Swift にブリッジすることでした。
したがって、プロトタイプの最終的なワークフローは次のようになります。
- 自動ロックをオフにして一時的にロック解除されたキーチェーンを作成します。
- Base64 でエンコードされた署名 ID データを環境変数 (Gitlab によって渡される) から取得してデコードします。
- 作成したキーチェーンに ID をインポートします。
- インポートされた ID に適切なアクセス オプションを設定して、Xcode や他のツールが共同設計のために ID を読み取れるようにします。
さらなるアップグレード
プロトタイプはうまく機能していたので、ツールに追加したい機能をいくつか特定しました。 私たちの目標は、最終的には fastlane を置き換えることでした。 「match」アクションはすでに実装されています。 ただし、fastlane は、プロビジョニング プロファイルのインストールとexport.plist の作成という、まだ提供されていない 2 つの貴重な機能を提供していました。
プロビジョニングプロファイルのインストール
プロビジョニング プロファイルのインストールは非常に簡単です。プロファイルの UUID を抽出し、UUID をファイル名としてファイルを `~/Library/MobileDevice/Provisioning Profiles/` にコピーします。Xcode がそれを適切に認識するには、これで十分です。 提供されたディレクトリをループし、その中で見つかったすべての .mobileprovision ファイルに対してそれを行う単純なプラグインをツールに追加することは、ロケット科学ではありません。
Export.plistの作成
ただし、export.plist の作成は少し複雑です。 適切な IPA ファイルを生成するために、Xcode はユーザーに、さまざまなソース (プロジェクト ファイル、資格付与 plist、ワークスペース設定など) から収集した特定の情報を含む plist ファイルを提供することを要求します。Xcode がそのデータを配布ウィザードを通じてのみ収集でき、収集できない理由は次のとおりです。 CLI 経由でのことは私にはわかりません。 ただし、プロジェクト/ワークスペースの参照と、Xcode プロジェクト ファイルの構築方法に関する少量の知識だけがあり、Swift API を使用してそれらを収集する必要がありました。
結果は予想よりも良かったので、これを別のプラグインとしてツールに追加することにしました。 また、より幅広いユーザー向けにオープンソース プロジェクトとしてもリリースしました。 現在、MQSwiftSign は、iOS アプリケーションの構築と配布に必要な基本的なファストレーン アクションの代替として使用できる多目的ツールであり、Miquido のすべてのプロジェクトで使用しています。
最終的な考え: 成功
Intel からiOS ARM アーキテクチャへの切り替えは困難な作業でした。 私たちは多くの障害に直面し、ドキュメントの不足によりツールの開発に多大な時間を費やしました。 しかし、最終的には堅牢なシステムを確立しました。
- 9 人の走者ではなく 2 人の走者を管理する必要があります。
- Rubygems の形で大量のオーバーヘッドを発生させることなく、完全に制御下にあるソフトウェアを実行します。ビルド構成から fastlane やサードパーティ ソフトウェアを取り除くことができました。
- macOS システムのセキュリティやセキュリティ フレームワーク自体、実際の Xcode プロジェクトの構造など、私たちが普段注意を払わない事柄についての多くの知識と理解。
喜んでお勧めします – iOS ビルド用の GitLab ランナーのセットアップに苦労している場合は、MQVMRunner を試してみてください。 単一のツールを使用してアプリを構築および配布する際にサポートが必要で、rubygems に依存したくない場合は、MQSwiftSign を試してください。 私にも効果があるし、あなたにも効果があるかもしれません!