M1 環境で Homebrew 導入コマンドを Xcode から叩けない場合の対処法

この記事は [YUMEMI Advent Calendar 2021](https://qiita.com/advent-calendar/2021/yumemi) 16 日目の記事です。

## 背景

M1 (Apple Silicon) 環境の Homebrew については、デフォルトのインストール先が `/opt/homebrew/bin` (+ `/opt/homebrew/sbin`) へと変更されました。

https://docs.brew.sh/FAQ#why-should-i-install-homebrew-in-the-default-location

この設定で Homebrew で導入したコマンドを Xcode の Build Phases 中で叩くと、 `command not found` のエラーが発生してしまいます。

https://github.com/yonaskolb/Mint/issues/201

https://github.com/realm/SwiftLint/issues/2992

Xcode のデフォルトの設定のままだと Build Phases 内部の Run Script において PATH に `/opt/homebrew/bin` が通っていない状態となってしまっているのが原因なので、これを何とか解消しようというのが今回の記事となります。

|対象|画像
|:-:|:-:|
|Build Phases|![print_path.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178830/7d64b73f-b9a6-84ba-93bf-3a47eddb5f43.png)|
|ログ|![print_path_result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178830/e692d99a-7c76-290f-9652-9b2501616a12.png)|

## 対処法
### その1. Run Script 内部で PATH を更新

Run Script での呼び出し直前に　PATH を明示的に更新してしまうというシンプルな対応です。

```sh:Run&#x20;Script
if [${PATH を更新する場合の条件}]; then
  # PATH の更新
fi

# 本来の Run Script 処理
```

ググってみると、おそらくこの対処法が一番ポピュラーなのですが、今回はこれを汎用的に利用できるように対応していきます。

まず、プロジェクトルートに `.envrc` ファイル (`.envrc` なのは [direnv](https://github.com/direnv/direnv) 等での間接利用狙い) を用意し、 `.gitignore` にこれを追加。

`.envrc` には、各開発者それぞれが必要な環境変数を記述してもらうようフローを構築します。

```sh:.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 それぞれに次の処理を追加してきます。

```sh:Run&#x20;Script
if [ -f "$SRCROOT/.envrc" ]; then
  source "$SRCROOT/.envrc"
fi

# 本来の Run Script 処理
```

これで、それぞれの Run Script の実行前に PATH (+ その他の環境変数) を設定、上書きしていまいます。

#### Pros

- プロジェクト外の環境を汚さない
- PATH 以外の環境変数にも対応可能
    - クレデンシャルな値を環境変数を利用して外部から注入している場合等

#### Cons

- プロジェクトの修正作業が発生する
    - 本来はプロジェクト側の問題とは言い難いため、筋の悪さを感じる
- Run Script 毎に対応する必要があり、記述が冗長 + 煩雑 になる

### その2. `UseSanitizedBuildSystemEnvironment` を無効化

試してみると分かるのですが、不思議なことにシェルから

```sh:Xcode&#x20;をシェルから起動
$ open XcodeProject.xcodeproj
```

のように Xcode を起動してみても、本記事冒頭のスクリーンショット画像のように Build Phases 内の Run Script では、 PATH を引き継いでくれないように見えます。

実はこれ、引き継いでくれないのではなく、 Xcode 側の隠し設定 (`UseSanitizedBuildSystemEnvironment`) によって PATH で設定されている値がフィルタリングされた結果となっています。 (たぶん)

該当の設定は 以下の `default` コマンドで無効化できるため、このコマンドを実行することによって、シェルから Xcode を起動した場合においては、問題が解消できます。

```sh
$ defaults write com.apple.dt.Xcode UseSanitizedBuildSystemEnvironment -bool NO
```

#### Pros

- プロジェクト側の修正は不要
- 設定自体が `defaults` コマンドを実行するだけで簡単

#### Cons

- Finder や Spotlight 等から Xcode を起動された場合に、 PATH 設定が効かない
    - 開発時には、 `open` して Xcode を起動するよう注意喚起が必要な可能性
    - ケアする場合は、[こういった対応](https://qiita.com/homu-konamilk/items/5886f3e48ffa658f4f78)が必要
- Xcode のデフォルト設定からの変更
    - `Sanitize` を意図しているので、セキュリティ的な懸念が前提にありそう
        - 想定されている expolit フローは不明だが、変な環境変数を追加されることを恐れている？
        - とはいえ、 CUI 上での `xcodebuild` 等では、シェル上の環境変数が利用される

### その3. プロジェクトローカルに該当コマンドを用意

そもそも、プロジェクトのビルドに必要なものを外部環境のコマンドに依存しているのがおかしいということで、プロジェクト内でコマンドを管理してしまう方法です。

https://qiita.com/hugehoge/items/0253695e8c4bd975f97f

のように、コマンドの実体を用意してしまう方法もあるのですが、今回はシンボリックリンクを利用します。

まずはプロジェクトルートに、 `bin/` ディレクトリを用意し、この中に利用するコマンドへのシンボリックリンクを用意します。

```sh:作業イメージ
$ cd ${プロジェクトルート}
$ mkdir bin && cd bin
$ ln -s /opt/homebrew/bin/${利用コマンド} ${利用コマンド名}
```

ディレクトリ構成と `.gitignore` は以下のような形となる想定です。

```sh:ディレクトリ構成
bin
├── .gitkeep
└── ${利用コマンド名} -> /opt/homebrew/bin/${利用コマンド}
```

```:.gitignore
/bin/*        # シンボリックリンクの設定は開発者環境毎に異なるため、 Git 管理から外す
!.gitkeep
```

プロジェクトローカルに利用コマンドを用意した後、 Run Script 側を修正して、プロジェクトローカルに配置したコマンド (`${SRCROOT}/bin/${利用コマンド名}`) を呼び出します。

#### Pros

- プロジェクト外の環境を汚さない
- ビルドで利用するコマンドの依存性が整理される

#### Cons

- プロジェクトの修正作業が発生する
- 対応手順がやや複雑
    - CI / CD 環境に対する対応も発生
    - 開発者がシンボリックリンクの設定で戸惑う可能性がある (特に iOS 開発エンジニアだと)

## まとめ

上記の 3 つの対応どれでも対処自体は可能なはずなのですが、オススメとしては

> **その1. Run Script 内部で PATH を更新**

の対応です。

PATH 以外の環境変数にも対応可能、かつ設定自体が明示的なので、いざというときのトラブルシュートも比較的容易な認識です。

特に、チーム開発の場合には開発者間の環境設定差異等もあるので、これを `.envrc` の記述 1 箇所で吸収してくれるというのは大きなメリットだと思っています。