この記事は YUMEMI Advent Calendar 2021 16 日目の記事です。
背景
M1 (Apple Silicon) 環境の Homebrew については、デフォルトのインストール先が /opt/homebrew/bin
(+ /opt/homebrew/sbin
) へと変更されました。
この設定で Homebrew で導入したコマンドを Xcode の Build Phases 中で叩くと、 command not found
のエラーが発生してしまいます。
Xcode のデフォルトの設定のままだと Build Phases 内部の Run Script において PATH に /opt/homebrew/bin
が通っていない状態となってしまっているのが原因なので、これを何とか解消しようというのが今回の記事となります。
対象 | 画像 |
---|---|
Build Phases | |
ログ |
対処法
その1. Run Script 内部で PATH を更新
Run Script での呼び出し直前に PATH を明示的に更新してしまうというシンプルな対応です。
if [${PATH を更新する場合の条件}]; then
# PATH の更新
fi
# 本来の Run Script 処理
ググってみると、おそらくこの対処法が一番ポピュラーなのですが、今回はこれを汎用的に利用できるように対応していきます。
まず、プロジェクトルートに .envrc
ファイル (.envrc
なのは direnv 等での間接利用狙い) を用意し、 .gitignore
にこれを追加。
.envrc
には、各開発者それぞれが必要な環境変数を記述してもらうようフローを構築します。
# for M1 mac
export PATH=$PATH:/opt/homebrew/bin:/opt/homebrew/sbin
# other env vars
export OTHER_ENV=e.g.credential_information
次に、 Build Phases 内の各 Run Script それぞれに次の処理を追加してきます。
if [ -f "$SRCROOT/.envrc" ]; then
source "$SRCROOT/.envrc"
fi
# 本来の Run Script 処理
これで、それぞれの Run Script の実行前に PATH (+ その他の環境変数) を設定、上書きしていまいます。
Pros
- プロジェクト外の環境を汚さない
- PATH 以外の環境変数にも対応可能
- クレデンシャルな値を環境変数を利用して外部から注入している場合等
Cons
- プロジェクトの修正作業が発生する
- 本来はプロジェクト側の問題とは言い難いため、筋の悪さを感じる
- Run Script 毎に対応する必要があり、記述が冗長 + 煩雑 になる
その2. UseSanitizedBuildSystemEnvironment
を無効化
試してみると分かるのですが、不思議なことにシェルから
$ open XcodeProject.xcodeproj
のように Xcode を起動してみても、本記事冒頭のスクリーンショット画像のように Build Phases 内の Run Script では、 PATH を引き継いでくれないように見えます。
実はこれ、引き継いでくれないのではなく、 Xcode 側の隠し設定 (UseSanitizedBuildSystemEnvironment
) によって PATH で設定されている値がフィルタリングされた結果となっています。 (たぶん)
該当の設定は 以下の default
コマンドで無効化できるため、このコマンドを実行することによって、シェルから Xcode を起動した場合においては、問題が解消できます。
$ defaults write com.apple.dt.Xcode UseSanitizedBuildSystemEnvironment -bool NO
Pros
- プロジェクト側の修正は不要
- 設定自体が
defaults
コマンドを実行するだけで簡単
Cons
- Finder や Spotlight 等から Xcode を起動された場合に、 PATH 設定が効かない
- 開発時には、
open
して Xcode を起動するよう注意喚起が必要な可能性 - ケアする場合は、こういった対応が必要
- 開発時には、
- Xcode のデフォルト設定からの変更
-
Sanitize
を意図しているので、セキュリティ的な懸念が前提にありそう- 想定されている expolit フローは不明だが、変な環境変数を追加されることを恐れている?
- とはいえ、 CUI 上での
xcodebuild
等では、シェル上の環境変数が利用される
-
その3. プロジェクトローカルに該当コマンドを用意
そもそも、プロジェクトのビルドに必要なものを外部環境のコマンドに依存しているのがおかしいということで、プロジェクト内でコマンドを管理してしまう方法です。
のように、コマンドの実体を用意してしまう方法もあるのですが、今回はシンボリックリンクを利用します。
まずはプロジェクトルートに、 bin/
ディレクトリを用意し、この中に利用するコマンドへのシンボリックリンクを用意します。
$ cd ${プロジェクトルート}
$ mkdir bin && cd bin
$ ln -s /opt/homebrew/bin/${利用コマンド} ${利用コマンド名}
ディレクトリ構成と .gitignore
は以下のような形となる想定です。
bin
├── .gitkeep
└── ${利用コマンド名} -> /opt/homebrew/bin/${利用コマンド}
/bin/* # シンボリックリンクの設定は開発者環境毎に異なるため、 Git 管理から外す
!.gitkeep
プロジェクトローカルに利用コマンドを用意した後、 Run Script 側を修正して、プロジェクトローカルに配置したコマンド (${SRCROOT}/bin/${利用コマンド名}
) を呼び出します。
Pros
- プロジェクト外の環境を汚さない
- ビルドで利用するコマンドの依存性が整理される
Cons
- プロジェクトの修正作業が発生する
- 対応手順がやや複雑
- CI / CD 環境に対する対応も発生
- 開発者がシンボリックリンクの設定で戸惑う可能性がある (特に iOS 開発エンジニアだと)
まとめ
上記の 3 つの対応どれでも対処自体は可能なはずなのですが、オススメとしては
その1. Run Script 内部で PATH を更新
の対応です。
PATH 以外の環境変数にも対応可能、かつ設定自体が明示的なので、いざというときのトラブルシュートも比較的容易な認識です。
特に、チーム開発の場合には開発者間の環境設定差異等もあるので、これを .envrc
の記述 1 箇所で吸収してくれるというのは大きなメリットだと思っています。