Bundlerを使ってgemをインストールしようとしたら`ffi`でエラーになった時の対処法

ffiに限ったことではないかも知れないけど、度々Bundlerでインストールしようと思うと、gemのコンパイルでコケることがある。

過去にffiだけじゃなくmysql2とかでもコケたことあった。

今回はffiの話になるけど、bundle installでコケた時の対処法とどこを調べたのかなどのメモを残しておく。

前提

  • 非エンジニアです
  • Ruby…というかプログラミングは勉強中です
  • Cのことはよくわかりません。なんもわからん

起こったこと

MiddlemanをBundlerでインストールしようとすると、ffiというgemのインストールでコケる

結論

  • ffiで使うコンパイラーが見つからなくてエラーになるっぽい
  • Homebrew使ってる場合は、libffiが入っているか確認してみる
  • libffiが入っている場合、LDFLAGSCPPFLAGSPKG_CONFIG_PATHの場所を確認してみる
  • PATHの場所は-L/usr/local/opt/libffi/**になってなかったら、
    • brew link --force libffiして、インストールされているlibffiへリンクをはる
    • シェルの設定にPATHの設定が書かれているか確認してみる
  • 上記が確認できたらbundle installしてみる
  • 成功したら解決です

余談

試して見てないのでわからないけど、これでも解決するかも知れないと思ったメモ。

  • ffiの公式READMEに書いてあるbundle config build.ffi --enable-system-libffibundle config build.ffi --disable-system-libffiとする

参考にした記事とか

github.com

github.com

qiita.com

エラー内容

Installing padrino-helpers 0.13.3.4
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2/ext/ffi_c
/Users/ksm/.rbenv/versions/3.0.0/bin/ruby -I /Users/ksm/.rbenv/versions/3.0.0/lib/ruby/3.0.0 -r ./siteconf20210205-86979-qhqxqz.rb extconf.rb
checking for ffi_prep_closure_loc() in -lffi... yes
checking for ffi_prep_cif_var()... yes
checking for ffi_raw_call()... yes
checking for ffi_prep_raw_closure()... yes
creating extconf.h
creating Makefile

current directory: /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2/ext/ffi_c
make "DESTDIR=" clean

current directory: /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2/ext/ffi_c
make "DESTDIR="
compiling AbstractMemory.c
compiling ArrayType.c
compiling Buffer.c
compiling Call.c
compiling ClosurePool.c
compiling DynamicLibrary.c
compiling Function.c
Function.c:847:17: error: implicit declaration of function 'ffi_prep_closure_loc' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    ffiStatus = ffi_prep_closure_loc(closure->pcl, &fnInfo->ffi_cif, callback_invoke, closure, code);
                ^
Function.c:847:17: note: did you mean 'ffi_prep_closure'?
/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/ffi/ffi.h:269:1: note: 'ffi_prep_closure' declared here
ffi_prep_closure(
^
1 error generated.
make: *** [Function.o] Error 1

make failed, exit code 2

Gem files will remain installed in /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2 for inspection.
Results logged to /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/extensions/x86_64-darwin-19/3.0.0/ffi-1.14.2/gem_make.out

An error occurred while installing ffi (1.14.2), and Bundler cannot continue.
Make sure that `gem install ffi -v '1.14.2' --source 'https://rubygems.org/'` succeeds before bundling.

In Gemfile:
  middleman was resolved to 4.3.11, which depends on
    middleman-core was resolved to 4.3.11, which depends on
      listen was resolved to 3.0.8, which depends on
        rb-inotify was resolved to 0.10.1, which depends on
          ffi

implicit declaration of function 'ffi_prep_closure_loc' is invalid in C99 ってところ。 無効な関数が使われているとのこと。 これが原因でmakeが失敗してるっぽい……っていうところはわかった。

やってみたこと

  • Bundlerを使わずにffigem installしてみる。
    • すんなりインストールできた。
  • Bundlerを使わずにMiddlemangem installしてみる。
    • すんなり終わった(Webrickがload errorになって動きはしなかったが…)

この辺は記事を書きながらログを探してたんだけど、残ってなかった。 作業ログを出力する方法があるっぽいので、今度ちゃんと設定しておこう…。

とにかくBundlerを使わずにMiddlemanffiもインストールできるので、こちらの問題ではなさそう?というアタリをつける。

ただ引っかかっているのはffiのインストールなので、ffiGitHubにいって似たような問題にぶち当たってる人がいないか探してみる。
結構いた。
直接「これをやれば解決する!」みたいなissueはなかなか見つけられなかったけど、gem ffi のインストールに失敗するときのコンパイラオプション指定方法 - Qiitaという記事を見つけて、Homebrewでlibffiをインストールしている環境ではlibffiのPATH指定がうまくいかないという記述を見つけて、それっぽいissueを探してみる。

Failed to build gem native extension on macOS High Sierra · Issue #653 · ffi/ffi

ちょっとバージョンなどは古いけどこの辺が一番近そう。 さらにREADMEもおさらいしてみる。

ffi/ffi: Ruby FFI

When installing the gem on CRuby (MRI), you will need:
A C compiler (e.g., Xcode on macOS, gcc or clang on everything else) Optionally (speeds up installation):
The libffi library and development headers - this is commonly in the libffi-dev or libffi-devel packages
The ffi gem comes with a builtin libffi version, which is used, when the system libffi library is not available or too old.

自分のHomebrewにlibffiが入ってるか確認してみる

❯❯❯ % brew list
[...]
docker-machine      git         libffi          luajit          pcre2           ruby
[...]

入ってるやんけ。(自分は入れた覚えがない)

macOSコンパイラー使わせるようにしたかったらlibffi邪魔かもしれないと思ってuninstallしようとしてみると

❯❯❯ % brew uninstall libffi                                                                                                                                                  ✘ 1
Error: Refusing to uninstall /usr/local/Cellar/libffi/3.3_2
because it is required by cairo, fontforge, glib, gobject-introspection, graphviz, gts, harfbuzz and pango, which are currently installed.
You can override this and force removal with:
  brew uninstall --ignore-dependencies libffi

えぇぇ(;´Д`)

とりあえず依存を無視してuninstallしてみる

❯❯❯ % brew uninstall --ignore-dependencies libffi                                                                                                                            ✘ 1
Uninstalling /usr/local/Cellar/libffi/3.3_2... (17 files, 540.5KB)
libffi 3.3 is still installed.
Run `brew uninstall --force libffi` to remove all versions.
❯❯❯ % brew uninstall --force libffi
Uninstalling libffi... (16 files, 489.5KB)

これでbundle installしたらいけるかな

❯❯❯ % bundle install
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.

[...]

Using middleman-cli 4.3.11
Installing ffi 1.14.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2/ext/ffi_c
/Users/ksm/.rbenv/versions/3.0.0/bin/ruby -I /Users/ksm/.rbenv/versions/3.0.0/lib/ruby/3.0.0 -r ./siteconf20210205-2342-l0vi57.rb extconf.rb
checking for ffi_prep_closure_loc() in -lffi... yes
checking for ffi_prep_cif_var()... yes
checking for ffi_raw_call()... yes
checking for ffi_prep_raw_closure()... yes
creating extconf.h
creating Makefile

current directory: /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2/ext/ffi_c
make "DESTDIR=" clean

current directory: /Users/ksm/dev/odde/vendor/bundle/ruby/3.0.0/gems/ffi-1.14.2/ext/ffi_c
make "DESTDIR="
compiling AbstractMemory.c
compiling ArrayType.c
compiling Buffer.c
compiling Call.c
compiling ClosurePool.c
compiling DynamicLibrary.c
compiling Function.c
Function.c:847:17: error: implicit declaration of function 'ffi_prep_closure_loc' is invalid in C99 [-Werror,-Wimplicit-function-declaration]
    ffiStatus = ffi_prep_closure_loc(closure->pcl, &fnInfo->ffi_cif, callback_invoke, closure, code);
                ^
Function.c:847:17: note: did you mean 'ffi_prep_closure'?
/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/usr/include/ffi/ffi.h:269:1: note: 'ffi_prep_closure' declared here
ffi_prep_closure(
^
1 error generated.
make: *** [Function.o] Error 1

make failed, exit code 2

マジか………死にたくなるな……(ぐったり)

libffiを使ってコンパイルするようにする

READMEにもThe libffi library and development headersが必要と書いてあるので、libffiを再インストールします。

❯❯❯ % brew install libffi
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 7 taps (puma/puma, homebrew/cask-versions, homebrew/core, homebrew/cask, homebrew/bundle, homebrew/services and rcmdnk/file).
==> New Formulae
aliddns                       cloudflare-wrangler           dasel                         libprelude                    msc-generator                 parliament
ansible@2.9                   coin3d                        grokmirror                    luv                           nuclei                        truffle
cherrytree                    cpplint                       htmltest                      mpdecimal                     osmcoastline                  vitess
==> Updated Formulae
Updated 4949 formulae.
==> Renamed Formulae
glibmm@2.64 -> glibmm@2.66                                                                pangomm@2.42 -> pangomm@2.46
==> New Casks
brewlet                             duckstation                         pathephone                          signet                              wolfram-engine
decloner                            gdat                                pokemon-trading-card-game-online    slippi-dolphin                      xcodes
deskreen                            kieler                              prezi-video                         spotter                             yesplaymusic
digital                             mathinspector                       sengi                               vine-server                         zy-player
==> Updated Casks
Updated 667 casks.
==> Deleted Casks
archi                                        barxtemp                                     oni                                          project-slippi-dolphin

==> Downloading https://homebrew.bintray.com/bottles/libffi-3.3_2.catalina.bottle.tar.gz
Already downloaded: /Users/ksm/Library/Caches/Homebrew/downloads/a70dda64619e2cda8bf245943e01e373ef2201564554472c957b150050571f65--libffi-3.3_2.catalina.bottle.tar.gz
==> Pouring libffi-3.3_2.catalina.bottle.tar.gz
==> Caveats
libffi is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

[...]

==> Pouring pango-1.48.1.catalina.bottle.tar.gz
🍺  /usr/local/Cellar/pango/1.48.1: 64 files, 3MB
Removing: /usr/local/Cellar/pango/1.48.0... (64 files, 3MB)
Removing: /Users/ksm/Library/Caches/Homebrew/pango--1.48.0.catalina.bottle.tar.gz... (733.4KB)
==> Checking for dependents of upgraded formulae...
==> No broken dependents found!
==> Caveats
==> libffi
libffi is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

For compilers to find libffi you may need to set:
  export LDFLAGS="-L/usr/local/opt/libffi/lib"
  export CPPFLAGS="-I/usr/local/opt/libffi/include"

For pkg-config to find libffi you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"

==> python@3.9
Python has been installed as
  /usr/local/bin/python3

Unversioned symlinks `python`, `python-config`, `pip` etc. pointing to
`python3`, `python3-config`, `pip3` etc., respectively, have been installed into
  /usr/local/opt/python@3.9/libexec/bin

You can install Python packages with
  pip3 install <package>
They will install into the site-package directory
  /usr/local/lib/python3.9/site-packages

See: https://docs.brew.sh/Homebrew-and-Python

For compilers to find libffi you may need to setFor pkg-config to find libffi you may need to setと書いてあるところがあるので、

.zshrcにPATHの設定を追加する

# Load path libffi with Homebrew
export LDFLAGS="-L/usr/local/opt/libffi/lib"
export CPPFLAGS="-I/usr/local/opt/libffi/include"
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig"

$SHELL -lで再起動して、bundle installしてみる

❯❯❯ % bundle install
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Using concurrent-ruby 1.1.8
Using minitest 5.14.3
Using thread_safe 0.3.6
Using public_suffix 4.0.6
Using backports 3.20.2
Using bundler 2.2.3
Using coffee-script-source 1.12.2
Using execjs 2.7.0
Using contracts 0.13.0
Using erubis 2.7.0
Using fastimage 2.2.2
Using tilt 2.0.10
Using hashie 3.6.0
Using rb-fsevent 0.10.4
Using memoist 0.16.2
Using thor 1.1.0
Using parallel 1.20.1
Using rack 2.2.3
Using servolux 0.13.0
Using i18n 0.9.5
Fetching ffi 1.14.2
Using temple 0.8.2
Using dotenv 2.7.6
Using fast_blank 1.0.0
Using rexml 3.2.4
Using uglifier 3.2.0
Using middleman-cli 4.3.11
Using tzinfo 1.2.9
Using haml 5.2.1
Using activesupport 5.2.4.4
Using coffee-script 2.4.1
Using padrino-support 0.13.3.4
Using hamster 3.0.0
Using padrino-helpers 0.13.3.4
Using addressable 2.7.0
Using kramdown 2.3.0
Installing ffi 1.14.2 with native extensions
Fetching sassc 2.4.0
Fetching rb-inotify 0.10.1
Installing rb-inotify 0.10.1
Installing sassc 2.4.0 with native extensions
Fetching listen 3.0.8
Installing listen 3.0.8
Fetching middleman-core 4.3.11
Installing middleman-core 4.3.11
Fetching middleman 4.3.11
Installing middleman 4.3.11
Bundle complete! 1 Gemfile dependency, 41 gems now installed.
Bundled gems are installed into `./vendor/bundle`

やっとできた………。お疲れ様だ、俺ちゃん。