bundle install でネイティブエクステンションのエラーが出たときの対処法

macOSにて、bundle install でネイティブエクステンションのエラーが出たときの対処法

Ruby のプロジェクトで、brew のパッケージをアップデートするとこういったエラーが出ることがよくあります。

rails aborted!
LoadError: dlopen(/path/to/yourproject/vendor/bundle/ruby/2.7.0/gems/pg-1.2.3/lib/pg_ext.bundle, 0x0009): Library not loaded: /opt/homebrew/opt/postgresql@14/lib/postgresql@14/libpq.5.dylib
  Referenced from: <14731482-0FF2-3BFC-A157-FF18C6DD56B6> /path/to/yourproject/vendor/bundle/ruby/2.7.0/gems/pg-1.2.3/lib/pg_ext.bundle
  Reason: tried: '/opt/homebrew/opt/postgresql@14/lib/postgresql@14/libpq.5.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/opt/homebrew/opt/postgresql@14/lib/postgresql@14/libpq.5.dylib' (no such file), '/opt/homebrew/opt/postgresql@14/lib/postgresql@14/libpq.5.dylib' (no such file), '/usr/local/lib/libpq.5.dylib' (no such file), '/usr/lib/libpq.5.dylib' (no such file, not in dyld cache) - /path/to/yourproject/vendor/bundle/ruby/2.7.0/gems/pg-1.2.3/lib/pg_ext.bundle
/path/to/yourproject/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require'
/path/to/yourproject/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `block in require_with_bootsnap_lfi'
以下略

この場合

Can’t find the ’libpq-fe.h header

↑これがエラーの原因です。

libpq-fe.h ファイルが無いことで、native extension がコンパイルできないため、エラーが出ています。 gem にはnative extension と呼ばれる物があり、これはC言語等で書かれたプログラムと、Rubyのプログラムをリンクすることで動作します。 bundle install を呼び出すと、内部的にはclang やgcc といった、C言語等をコンパイルするプログラムを呼び出して、native extension をコンパイルします。 clang やgcc は ヘッダーファイルという.h や.hh .hpp 等の拡張子のファイルを探してコンパイルします。 このファイルを探すフォルダのデフォルトは /usr/include /usr/local/include /opt/homebrew/include あたりです。 逆にいうと、このフォルダにヘッダーファイルがないと、native extension はコンパイルできません。

find /opt/homebrew -name libpq-fe.h で探してみると、

/opt/homebrew//Cellar/postgresql@15/15.3/include/libpq-fe.h

このような結果になりました。

前述の通り、clang は /usr/include /usr/local/include /opt/homebrew/include あたりを探しますので、/opt/homebrew//Cellar/postgresql@15/15.3/include に libpq-fe.h ファイルがあっても、ファイルを見つけることができず、コンパイルは失敗してしまいます。

そこで、

brew link postgresql@15

このコマンドを実行すると、/opt/homebrew/include にシンボリックリンクが貼られます。 find /opt/homebrew -name libpq-fe.h で探してみると、

/opt/homebrew/include/libpq-fe.h
/opt/homebrew/Cellar/postgresql@15/15.3/include/libpq-fe.h

のように、シンボリックリンクが貼られていることがわかります。

clang が探しに行くディレクトリに、libpq-fe.h が配置された状態になりました。これにより、bundle install が成功するようになったはずです。

clang に、ヘッダファイルを認識させる方法は2つあります。

  1. brew link をつかって、インストールしたパッケージのヘッダファイルを、/opt/homebrew/include にリンクする
  2. bundle config をつかって、clang 起動時のオプションに -I オプションを付加して、/usr/include /usr/local/include /opt/homebrew/include 以外のディレクトリも、include ディレクトリとして認識するようにする

今回は前者の方法を記載しました。 後者の場合だと、プロジェクトごとに、リンク先のディレクトリを別にすることができるので、古いバージョンのパッケージを使うプロジェクトと、新しいバージョンのパッケージを使うプロジェクトを同時に開発する場合に便利です。

QAコーナー

ヘッダファイルの所在が/usr/include /usr/local/include /opt/homebrew/include のどちらかではないことって結構あるんでしょうか?

それは結構あります。 そもそも postgresql@15 はインストールしただけでは .h ファイルが include に入りません。

パッケージには色々なタイプのものがあります

  1. 実行ファイル+ソースを含むもの
  2. 実行ファイルしか無いもの
  3. ちなみにUbuntu等のパッケージには、ソースだけのパッケージっていうのもあります

1のタイプのパッケージは、パッケージをメンテナンスしてくれている人たちが、いい感じに /opt/homebrew/include にヘッダファイルを配置してくれます。 しかしpostgresql@15 は、2の実行ファイルしか無いもの というカテゴリのパッケージとして定義されています。 そのため、インストールしただけでは /opt/homebrew/include にヘッダがインストールされません。 brew のパッケージ名に @15 とついているものは、2のタイプのパッケージです。

1のタイプのパッケージの場合でも、ヘッダファイルが /opt/homebrew/include にインストールされない場合があるのでしょうか?

ありません。 しかし、いい感じにヘッダファイルをが配置されていても bundle install でエラーが出る可能性があります。 多くの場合、 gem が必要としているヘッダファイルが定義されていたパッケージのバージョンと、インストールされているパッケージのバージョンが一致していないことが原因です バージョンが一致しないせいで、例えばヘッダファイル自体がなくなっていたり、ヘッダファイルは存在していても、中に定義されている関数が変わってしまうことがあります。 これによりコンパイルやリンクに失敗します。

Native Extension でエラーが出るパターンは他にもありますか?

brew ではなく、mac のアップデートが原因でエラーが引き起こされる可能性もあります。 mac には数多くのOSSが初めからインストールされており、アップデートによってライブラリのパスが変わってしまったり、ライブラリに定義されていた関数がなくなったり刷ることがあります。 前者の場合は、bundle update ではなく、rm -rf vendor/bundle && bundle install でも解消する可能性があります。 ライブラリに定義されていた関数がなくなったことが原因の場合は bundle update をするか、gem をアップデートするのではなく、brew などで古いバージョンのパッケージをインストールする必要があります

もともと動いていたのに、突然エラーが出る場合は、以下の原因を中心に考えてみるといいかもしれません。

  1. native extension が要求しているパッケージがインストールされていない
  2. native extension が要求しているパッケージは、ソースファイルを所定のディレクトリに配置していない
  3. native extension が要求しているパッケージのバージョンが古いまたは新しくて、brew でインストールされているパッケージのバージョンと一致しない
  4. native extension が要求しているパッケージが要求しているパッケージが、macOSによってインストールされていたが、macOSのアップデートによって、パッケージのパスやバージョンが変わってしまった

それらのうちどれがその時の原因なのかを判断するにはどうすればいいですか?

  1. エラー文をしっかり読む
  2. Gemfile.lock を読んで、プロジェクトが要求しているgem を把握する
  3. 自分のコンピューターに何のパッケージのどのバージョンが何によって(macOS または、brew またはそれ以外によって)インストールされているかを把握する
  4. プロジェクトが要求している gem はどのバージョンのパッケージを要求しているかを把握する

が必要です そのうち、4は定型的な方法で調べることができません。GithubのIssueを読んだり、公式Webサイトを調べたり、場合によってはソースを見る必要があります。なので、4はまあまあハードルが高いです。 また、1-3 が把握できてれば4は把握しなくても、brew を下げるか、プロジェクトで利用しているgem を壊れない程度にちょっとづつあげるという方法で解消できることが多いです。 バージョンが分からなくても、ヤマ勘で上げたり下げたりすればそのうち合致します。

自分のMacに何が入ってるのか把握するにはどうすればいいですか?

  • which 該当のプログラム
  • find / -name 該当のプログラム
  • 該当のプログラム -v
  • brew info 該当のプログラム
  • brew list

辺りを叩くと、バージョンを確認することができます。

この記事をシェア

弊社では、一緒に会社を面白くしてくれる仲間を募集しています。
お気軽にお問い合わせください!