エンジニアリング実践

クラウドMac CIでRubyとfastlaneを分離する実践手順

クラウドMac CIでRubyとfastlaneを分離する実践手順

同じクラウドMacでも、開発者がターミナルから実行した fastlane は成功するのに、CIジョブではgem不足と表示されることがあります。runnerを修正すると、今度はログインシェルが別のRubyを参照する場合もあります。こうした障害の多くはfastlane自体ではなく、システムRuby、バージョンマネージャー、Bundler設定、ジョブ環境の組み合わせによるパスのずれが原因です。解決策はgemを何度もインストールし直すことではなく、Rubyツールチェーン全体をプロジェクトの一部として管理することです。

実際に実行されているRubyを確認する

最初からインストールコマンドを実行してはいけません。ログインシェルとCIジョブのそれぞれで、次の結果を記録します。

command -v ruby
ruby -v
command -v bundle
bundle -v
gem env home
bundle config list
printf '%s
' "$PATH"

特に、rubybundle が同じプレフィックス配下にあるかを比較してください。Rubyがバージョンマネージャーのディレクトリにあり、Bundlerがシステムディレクトリにある場合、その後のインストール先が誤ったgemパスになる可能性があります。CIに GEM_HOMEGEM_PATHBUNDLE_PATH が設定されていないかも確認します。残存している環境変数は、プロジェクト設定を上書きします。

対話型ターミナルで実行できても、自動ジョブが同じ環境を持つとは限りません。CIは、シェルの起動スクリプトが偶然PATHを書き換えることに依存せず、明示的なバージョンファイルとプロジェクト設定から起動する必要があります。

診断用の成果物は1つ作成すれば十分です。ログにはトークン、証明書の内容、環境変数全体を出力しないでください。ほとんどの解決先の問題は、パスとバージョンだけで判断できます。

Ruby、Bundler、fastlaneのバージョンを固定する

リポジトリのルートには .ruby-versionGemfileGemfile.lock をコミットします。Rubyのバージョンには、曖昧な 3.3 ではなく、インストール可能な完全なバージョンを指定する必要があります。まずチームで使用するバージョンを決めてから、次を実行します。

printf '%s
' '3.3.6' > .ruby-version
bundle init
bundle add fastlane --version '~> 2.0'
bundle lock

Gemfile は許容するバージョン範囲を表し、Gemfile.lock は今回解決された正確なバージョンを記録します。CIでビルドのたびに bundle update を実行してはいけません。それではロックファイルによる制約が機能しなくなります。インストール後は、すべてのコマンドをBundler経由で実行します。

bundle exec fastlane --version
bundle exec fastlane ios ci

グローバルな fastlane をショートカットとして使用しないでください。別のRubyから提供されている可能性があるほか、システム更新や手動デバッグの後に内容が変わることもあります。プロジェクトで特定のBundlerバージョンが必要な場合は、Gemfile.lockBUNDLED WITH セクションからバージョンを読み取り、そのバージョンをインストールしてから bundle install を実行します。

依存関係のインストール先をプロジェクトディレクトリに限定する

プロジェクト単位の設定は、リポジトリに隣接する .bundle/config に書き込みます。ホスト全体に適用されるBundlerのグローバル設定は変更しないでください。

bundle config set --local path vendor/bundle
bundle config set --local clean true
bundle install --jobs 4 --retry 2

通常、vendor/bundle はGitにコミットせず、CIキャッシュで管理します。これによりシステムgemを汚染せず、複数のプロジェクトで異なるバージョンを使用できます。インストール後は、次のチェックで解決経路が完全であることを確認できます。

test -f Gemfile.lock
test "$(ruby -e 'print RUBY_VERSION')" = "$(cat .ruby-version)"
bundle check
bundle exec ruby -e 'require "fastlane"; puts Fastlane::VERSION'

最後のコマンドが失敗しても、すぐにすべてのキャッシュを削除してはいけません。まず bundle config listbundle env を確認し、ジョブが現在のリポジトリのGemfileを読み込んでいることを確かめます。親ディレクトリのGemfileや、BUNDLE_GEMFILE で指定された別のファイルを参照していないか確認してください。

クリーンな非対話環境を再現する

runnerへ正式に組み込む前に、最小限の環境で一度検証できます。

RUBY_BIN="$(dirname "$(command -v ruby)")"
env -i \
  HOME="$HOME" \
  PATH="$RUBY_BIN:/usr/bin:/bin:/usr/sbin:/sbin" \
  BUNDLE_GEMFILE="$PWD/Gemfile" \
  bundle exec fastlane --version

この手順により、個人用のシェル設定だけに記述されたパス、エイリアス、環境変数を事前に検出できます。コマンドが失敗する場合は、CIに個人設定全体を読み込ませるのではなく、runnerの明示的な初期化処理を修正してください。

混在を防ぐキャッシュキーを設計する

ネイティブ拡張を含むgemのキャッシュキーを、Gemfile.lock だけで構成してはいけません。Rubyのバージョン、CPUアーキテクチャ、macOSのメジャーおよびマイナーバージョンが異なると、コンパイル済みファイルを読み込めない場合があります。キャッシュキーには、少なくとも次のフィールドを含めます。

フィールド 取得方法 目的
Rubyバージョン ruby -e 'print RUBY_VERSION' ABIの違いを分離
CPUアーキテクチャ uname -m 異なるネイティブコードを区別
macOSバージョン sw_vers -productVersion システムライブラリの違いを回避
ロックファイルのハッシュ shasum -a 256 Gemfile.lock 依存関係の変更時に無効化

次のように、安定したフィンガープリントを生成できます。

RUBY_VERSION_KEY="$(ruby -e 'print RUBY_VERSION')"
ARCH_KEY="$(uname -m)"
OS_KEY="$(sw_vers -productVersion | cut -d. -f1,2)"
LOCK_KEY="$(shasum -a 256 Gemfile.lock | cut -d' ' -f1)"
printf '%s-%s-%s-%s
' "$RUBY_VERSION_KEY" "$ARCH_KEY" "$OS_KEY" "$LOCK_KEY"

キャッシュを復元した後も、必ず bundle check を実行します。チェックに失敗した場合にのみ bundle install を実行して不足分を補います。「キャッシュの展開に成功した」ことを、依存関係が利用可能である証明として扱ってはいけません。

障害を特定可能にする検証ゲートを設ける

最終的なジョブは、「環境の検証」「依存関係の検証」「業務lane」の3段階で実行します。第1段階でバージョンとパスを確認し、第2段階で bundle check を実行してから、第3段階でfastlaneを呼び出します。いずれかの段階が失敗した場合は直ちに停止し、後続処理で理解しにくいプラグイン不足や定数読み込みエラーとして現れることを防ぎます。

次の項目をマージチェックに追加することを推奨します。

  • .ruby-version とrunnerが実際に使用するRubyが完全に一致している。
  • Gemfile.lock がコミットされており、ジョブの実行前後で変更されていない。
  • fastlaneは bundle exec 経由でのみ呼び出されている。
  • .bundle/config がプロジェクト単位の依存関係ディレクトリを使用している。
  • キャッシュキーにRuby、アーキテクチャ、システムバージョン、ロックファイルのハッシュが含まれている。
  • ログにはバージョン、パス、検証結果だけを残し、機密性の高い環境変数の値を出力していない。
  • runnerとログインシェルが、同じ明示的な初期化ロジックを使用している。

これらの制約を適用すれば、Rubyツールチェーンは「ホストにインストール済みのコマンド群」ではなく、監査可能なビルド入力になります。以後Rubyやfastlaneをアップグレードするときは、バージョンファイルとロックファイルを独立したコミットで更新し、同じ検証手順で変更範囲を確認できます。障害が発生してから、ジョブがどの環境を呼び出したのか推測する必要はありません。

よくある質問

Gemfile.lockを保存してもfastlaneのバージョンが変わるのはなぜですか?

グローバルのfastlaneを直接実行するか、実行前にロックファイルを更新している可能性があります。bundle exec fastlaneを使い、ロックファイルを固定入力として扱います。

vendor/bundleを複数のクラウドMacで共有できますか?

無条件には共有できません。Rubyのバージョン、CPUアーキテクチャ、macOSのメジャー・マイナーバージョン、Gemfile.lockのハッシュをキャッシュキーに含めます。

CIだけ成功して対話シェルで失敗する場合は何を確認しますか?

command -v ruby、ruby -v、bundle config list、環境変数を比較します。PATHやRuby管理ツールの初期化方法が異なるケースが一般的です。

専有物理ノード

クラウドでMac miniワークフローを継続運用

RunAMac M4、レンタル期間、デプロイ先ノードを選び、開発、ビルド、実験のタスクを専有物理ノードで実行できます。

レンタルプランを選ぶ