M1 Macで、arm64とx86_64のruby環境を共存させて、Flutterの環境構築

# 概要

- MacbookAir BigSur, Apple M1 チップ

において、

- `Flutter`(AndroidStudio)

の開発環境を最低限構築したが、
とてもとても大変だったのでメモを残す。

**m1 Macにおいて`ruby`環境をどうコントロールするか**、
という点がメインの話になるので、`Flutter`に関わらず **ruby周りで困っている人** は見て頂ければ幸いです。

以下の方針。

- `arm64`と`x86_64`、それぞれの独立ターミナル環境を構築
- `ruby`はそれぞれ別個にインストールしなおす(macデフォルトrubyを使わない)
- AndroidStudio は内部で`x86_64`でターミナルを起動しているようなので、`x86_64`の方の環境でflutter環境を整える
- **`arch -x86_64`コマンドは使わない！！**

特に最後。
M1 Macで`cocoapods`周りで上手く行かないときに、解決策として、

```bash
arch -x86_64 sudo gem install cocoapods
```

がサイトによく載っているが、相当しっかり理解している人でない限り、
これはしない方が良いので注意が必要(最後に詳しく述べます)。

おまけ

- AndroidStudioのPreferenceを開くとフリーズする問題
    - 以下の記事を参考にさせて頂きました
        - [MacOS Big Sur で Android Studio がフリーズする問題の対処法](https://zenn.dev/sasumasa/articles/41410797817d1c)
    - タブでPreferencesを開こうとするとフリーズするようです
    - 自分の環境ではデフォルト設定だと、フルスクリーン状態でなければ新規ウィンドウで開かれるので、AndroidStudioをフルスクリーンではなくした後にPreferencesを開くことで解決しました

# M1チップ周りの前提知識

詳しくないので厳密には正しくない情報かもしれないが、
以下のような概念をなんとなく把握しておく必要がある。

- M1チップ: `arm64`(略して`a64`)
    - Appleシリコンとも呼ぶ
    - `arm64e`との違いはなんだろう、、、
- Intelチップ: `x86_64`
- M1のMacでも、 `x86_64` でアプリケーションを起動したりコマンド実行したりできる
    - Finderからアプリケーションの`情報を見る`で、`Rosettaを使用して開く`をオンにする
    - `arch -x86_64`をコマンドの先頭につけてコマンドを実行する
        - `arch -x86_64 sudo gem install hogehoge`
            - 個人的にはこれは危険なのでやらない方が良いという結論に(後述)
        - これを応用すればRosettaのオプションを使わなくても`x86_64`のシェルを立ち上げられる
            - `exec arch -x86_64 /bin/zsh`
- `uname -m`コマンドで`arm64`か`x86_64`かを確認できる

# `arm64`と`x86_64`の各ターミナル環境を構築

次の記事を大変参考にさせて頂きました。
この記事に出会わなければどれだけ迷走していたことか、、、大変感謝です。

- [Yet Another な Apple Silicon (M1) + Homebrew のベストプラクティス](https://qiita.com/sprout2000/items/b8ed210a4babb94f2a67)

以下実際自分が行った手順

## `x86_64`版ターミナルアプリの作成

[Apple Silicon (ARM, M1) のmacOSにaarch64とx86_64のhomebrewを共存させる](https://qiita.com/chibiegg/items/43b7604a589e3760e88e)
を参考にさせて頂きました。アプリをコピーペーストして増やすという発想にびっくり。

- Finderでターミナルアプリをコピーペーストして複製
    - 自分は`iterm2`ですが、デフォルトのターミナルアプリでも何でもいい(Rosettaがちゃんと効けば)
- 複製した方のアプリを`ターミナル_x86_64`のような名前に設定
- 複製した方だけ、`情報を見る`から`Rosettaを使用して開く`をオンにしておく
- これで`arm64`で動かしたい時は普通のターミナル、`x86_64`で動かしたい時は`ターミナル_x86_64`を起動すればok
    - `uname -m`コマンドで今のターミナルがどちらか確認できる

## 双方で`homebrew`をインストール

- 順番が関係あるか分からないが、`arm64` -> `x86_64` の順で自分は行った
- それぞれで次のコマンドを実行 ([brew公式](https://brew.sh/)のコマンド通り)

```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

ちなみにhomebrewが`arm64`に対応していないという情報がよく出てきますが、
今は対応済みなので普通にインストールできます。

## 双方で`ruby`をインストール

```bash
brew reinstall -s ruby
```
- sudoは付けない！
- そもそもM1 Macの場合はデフォルトの`ruby`を使うのは非推奨らしい
    - [Ruby on Apple Silicon M1 Macs: The Problems, the Findings, and the Workarounds](https://betterprogramming.pub/ruby-on-apple-silicon-m1-macs-fb159849b2f5)

## flutterをインストール

ここはターミナルとか`arm64`とか`x86_64`とかは関係ない

[flutterインストールページ](https://flutter.dev/docs/get-started/install/macos
)からzipをダウンロードしてインストール

ターミナル構築が一通り完了してからやってもいいが、
パス設定を一気にやってしまいたいのでこのタイミングでflutter入れておくのが効率的。

## `ruby`と`flutter`のパスを設定

- `~.zshrc`ファイル等に以下のように設定することで`arm64`,`x86_64`双方で独立して動作するようにする
- `arm64`の`ruby`系
    - `ruby`: `/opt/homebrew/opt/ruby/bin`
    - `gems`: `/opt/homebrew/lib/ruby/gems/x.x.x/bin` (`x.x.x`は実際に確かめる)
- `x86_64`の`ruby`系
    - `ruby`: `/usr/local/opt/ruby/bin`
    - `gems`: `/usr/local/lib/ruby/gems/x.x.x/bin` (`x.x.x`は実際に確かめる)

```bash
if [[ $(uname -m) = "x86_64" ]]; then
  typeset -U path PATH
  path=(
    /usr/local/opt/ruby/bin
    /usr/local/lib/ruby/gems/x.x.x/bin
    /usr/local/bin
    /usr/local/sbin
    /usr/bin
    /bin
    /usr/sbin
    /sbin
    $HOME/flutter/bin
  )
else
  typeset -U path PATH
  path=(
    /opt/homebrew/bin
    /opt/homebrew/sbin
    /opt/homebrew/opt/ruby/bin
    /opt/homebrew/lib/ruby/gems/x.x.x/bin
    /usr/local/bin
    /usr/local/sbin
    /usr/bin
    /bin
    /usr/sbin
    /sbin
    $HOME/flutter/bin
  )
fi
```

- パスの順番は必要に応じて調整
    - 前にある方が優先
- 今後新しいパスを追加する必要があれば、双方に追加する
    - もちろん分けて管理する必要があるものであれば別個に追加する

<details>
<summary>細かい話</summary>
<div>

export文を使うと何度もシェルをリロードする度に重複してパス設定がなされてしまうので、
path変数をここで完全に上書きしている。
特に`arch`コマンドによってシェル自体を切り替える想定のある人はこのような書き方の方が安心。
しかし、今回はターミナルアプリ毎分けたので、
リロードによる重複が気にならない人は、このような書き方ではなくても、
普通にexportの羅列で問題ないはず。(if分岐自体は必要)
その場合は、/etc/pathsで事前に読み込まれているであろう `/usr/local/bin` ~ `/sbin`までの設定は省いていい。
ただし優先度には気を付ける必要がある。(`$PATH`の前に追加するのか後ろに追加するのか)

</div>
</details>

## 各ターミナルの確認

双方のターミナルで、以下を確認

- `uname -m`コマンドを打つと、対応するアーキテクチャ名(`arm64`or`x86_64`)が返ってくること
    - ここがおかしい場合は、コピーして作ったターミナルアプリの方にRosettaの設定ができていない可能性がある
- `ruby -v`コマンドを打つと、アーキテクチャに応じて以下のように返ってくること
    - `arm64`: `ruby x.x.xoo (xx revision oo) [arm64-xxoo]`
    - `x86_64`: `ruby x.x.xoo (xx revision oo) [x86_64-xxoo]`
    - ここがおかしい場合は、パス設定が不適切な可能性がある

これで、それぞれのターミナルで独立したruby環境を使っているということになる！
これが本記事で一番重要なところ。

# `AndroidStudio`と`Xcode`のインストール

ここら辺は普通です。他にも詳しく解説してくださっているサイトがたくさんあります。

- `AndroidStudio`インストール
    - [AndroidStudioインストールページ](https://developer.android.com/studio/index.html)
    - 一度起動してセットアップを実行
    - 一部エミュレータでエラーが出るが、とりあえず気にしない！
        - とりあえず一部のエミュレータがダメでも基本的には無事動作します
        - もし気になる人はここら辺詳しく解説して下さっているサイトが他にたくさんあると思うのでそちらを！
    - pluginインストール
        - `Configure` -> `Plugins` で `Flutter` をインストール
            - `Dart` は自動で入るはず
- `Xcode`インストール
    - AppleStoreからインストール

# Flutter環境構築

やっと本題。
`flutter doctor`コマンドで進めていくわけだが、重要なことは、

- **x86_64ターミナルの方を開いて環境構築を進める**
- **gemにsudoをつけない**

ということ！

詳しい仕組みはわからないが、`AndroidStudio`が内部で呼び出すシェルは、
`x86_64`の`/bin/zsh`になっている。
パス設定も、`x86_64`側のパス設定が`AndroidStudio`からコマンド実行される際に呼び出される。
なので、少なくとも`x86_64`側のターミナルで構築を行う必要がある。
(自分は一応双方とも構築しました)

上記の手順でターミナル環境を構築すると、sudoを使わずにgemを利用できる。
sudoをつけて操作してしまうと、例えば`gem install`をした際にはそのgemだけ権限がおかしくなったりするのでよくない。
(gemsの配下をllしてみるとよくわかる)


## cocoapodsのインストール

```bash
gem install cocoapods
```

**sudoを付けない！**

<details>
<summary>cocoapodsが見つからないエラー</summary>
<div>

cocoapodsを入れたはずなのにAndroidStudioで見つからないという問題が、ネット上でもよく挙がっています。
自分はその問題の1つの可能性として、AndroidStudioが内部で呼び出すシェルのパスと、
`gem install`を実行した環境のパスの不一致があるのではないかと思います。

AndroidStudioはデフォルトだと、`/bin/zsh`を`x86_64`で内部起動していそうです。
なので、今回みたいにアーキテクチャによって環境変数を分けて管理してる場合は注意が必要です。

自分は最初`arm64`側で構築し、`flutter doctor`が全部OKになりましたが、
AndroidStudioでいざビルドをしてみると`cocoapods`が無いというエラーが発生しました。
AndroidStudioが呼び出す`x86_64`のシェルでは、`arm64`側のgemsにパスが通っていないので、至極当然なエラーでした。
それに気づいて、`x86_64`側でもcocoapodsを入れたら解決した、という流れでした。

</div>
</details>

## `flutter doctor`の指示に沿って環境構築

`flutter doctor`コマンドの指示に従って構築する！
(詳しく解説してくださっているサイトがたくさんあるのでここでは省略)
確か以下を行う必要があった。

- AndroidStudio系でライセンス承諾
- xcode系で2つほどよくわからないコマンドを打たされた

以上で無事完了！
M1 MacでFlutterを用いたアプリ開発できるはずです！

# archコマンドを推奨しない理由

ハマるポイントはruby, gem周りと思います。
特にarm64系で`cocoapods`がうまく動かない、というサイトをたくさん目にしました。

よく解決方法として、

```bash
arch -x86_64 sudo gem install cocoapods
```

等々が記載されていますが、自分はこの方法は非推奨と思います。
しっかと理解して行うならばよいのですが、
何となくこれで解決するだろうと思ってこのコマンドを実行してしまうのは危険です。

それは、同じruby環境の中でgemを、`arm64`と`x86_64`とを混ぜて操作することになるからです。
これは1つ間違えればgem全体を破壊することになりかねません。
(自分は実際に一度やらかしました。まったくgemコマンドが動作しなくなり、ruby毎再インストールする羽目になりました。)

色々迷走した挙句にたどり着いたのが、

- [Yet Another な Apple Silicon (M1) + Homebrew のベストプラクティス](https://qiita.com/sprout2000/items/b8ed210a4babb94f2a67)
- [Apple Silicon (ARM, M1) のmacOSにaarch64とx86_64のhomebrewを共存させる](https://qiita.com/chibiegg/items/43b7604a589e3760e88e)

この方々の記事でした。
これらの記事に`brew`を`arm64`用と`x86_64`用とで別々に共存させるという発想を頂き、
これを利用して`ruby`も別個管理できるのではないかと思い至り、
試してみたらうまくいきました。

`arm64`用と`x86_64`用とで分離して管理できれば、
この地獄のような問題をすっきりと解決できますね！

# おわりに

m1 Macを買ったことを後悔しかけていましたが、
今はむしろ良かったとも思えるくらいです。

それくらい納得のいく環境を整えることができました。
これだけ分かればFlutter環境だけでなく、
そのほかのアプリケーションやフレームワークも何とかなりそうと思います。

残念なのはAndroidStudioは`x86_64`で動いていそうなことですね、、
せっかくなので`arm64`で動かすことはできないのだろうか、、