雑記

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|09|11|
2009|02|03|05|06|07|08|10|11|12|
2010|01|03|04|05|06|07|08|09|10|
2011|05|06|09|10|
2012|03|07|09|12|
2013|01|02|04|05|07|08|10|11|
2014|04|05|08|10|12|
2015|01|05|
2016|09|

2013-08-18 [長年日記]

[Ruby]Rubyライブラリのパスの謎に迫る

前回のサーチパスの問題がどうにも気持ち悪いので調べてみました。先に結論を書いておくと、Ruby本体のコンパイル時のオプションによってライブラリのサーチパスが変わり、その影響でbundlerがgemを見つけられなくなるという不具合のようです。

まずは問題のおさらいですが、tDiaryのtarボールを展開するとgemのファイルが".bundle/ruby/1.9.1/"以下に置かれます。ところが、私の環境ではこのパスを見つけられずエラーになってしまいました。調べてみると、

  • bundle installすると動くようになる
  • この時、gemは".bundle/ruby/1.9/"以下にインストールされる
  • 元の".bundle/ruby/1.9.1/"を".bundle/ruby/1.9/"に変更したり、シンボリックリンクを張ればそれらのファイル群でもちゃんと動く

という状況でした。また、@machuさんの環境では1.9.3でもそのままで問題無く動くということでした。とここまでが昨日の時点での話で、ライブラリのサーチパスがかなり怪しいです。

さて昨日も書いたのですが、Ruby関連はFreeBSDのportsを利用しているので、調べてみると、/usr/ports/Mk/bsd.ruby.mk に

  :
RUBY_DEFAULT_VER?=   1.9

RUBY_VER?=     ${RUBY_DEFAULT_VER}
  :
# Directories
RUBY_LIBDIR?=     ${_RUBY_SYSLIBDIR}/ruby/${RUBY_VER}
RUBY_ARCHLIBDIR?= ${RUBY_LIBDIR}/${RUBY_ARCH}
RUBY_SITELIBDIR?= ${_RUBY_SITEDIR}/${RUBY_VER}
RUBY_SITEARCHLIBDIR?=   ${RUBY_SITELIBDIR}/${RUBY_ARCH}
RUBY_VENDORLIBDIR?=  ${_RUBY_VENDORDIR}/${RUBY_VER}
RUBY_VENDORARCHLIBDIR?= ${RUBY_VENDORLIBDIR}/${RUBY_ARCH}
  :
GEMS_BASE_DIR= lib/ruby/gems/${RUBY_VER}
GEMS_DIR=   ${GEMS_BASE_DIR}/gems
DOC_DIR= ${GEMS_BASE_DIR}/doc
CACHE_DIR=  ${GEMS_BASE_DIR}/cache
SPEC_DIR=   ${GEMS_BASE_DIR}/specifications
GEM_NAME?=  ${PORTNAME}-${PORTVERSION}
GEM_LIB_DIR?=  ${GEMS_DIR}/${GEM_NAME}
  :

/usr/ports/lang/ruby19/Makefileに

post-patch:
   @${REINPLACE_CMD} -E \
      -e 's,-l$$pthread_lib,${PTHREAD_LIBS},g' \
      -e '/^RUBY_LIB_PATH/s,\.\$$\{TEENY\},,' \
      -e '/^RUBY_SITE_LIB_PATH2/s,\.\$$\{TEENY\},,' \
      -e '/^RUBY_VENDOR_LIB_PATH2/s,\.\$$\{TEENY\},,' \
      ${WRKSRC}/configure

という記述がありました。ところが、これが原因かと思ってさらに追ってみてもどうにもおかしい。前者はパッケージを作るためのパスの設定でコンパイル時には使用されておらず、後者はTEENYを削除していてそれっぽいのですが、ソースを展開してできるconfigureスクリプトには該当する行が無く、さらに探すと展開されたソースツリーのdoc/ChangeLog-1.9.3に

Thu Feb  5 11:21:35 2009  Nobuyoshi Nakada  <nobu@ruby-lang.org>

   * configure.in (RUBY_LIB_VERSION): added for library version, to split from core version.  [ruby-dev:37748]

   * configure.in (RUBY_LIB_PATH, etc): moved actual version dependent stuff to version.c.

と2009年の2月にはconfigureから消されているようで、今現在は意味の無いコードのようでした。よく分からないのでsandboxを作ってRubyをportsを使わずにソースからインストールしてみると、ちゃんと(?)/usr/local/lib/ruby/1.9.1/以下にインストールされるので、ますます謎です。

ここでいったんRubyの追跡をあきらめ、gemのサーチパスが変わってしまう原因を追ってみますが、こちらのportsはソースには全く手を入れておらずにsetup.rbを実行しているだけでした。いよいよ深みにはまってきたなぁと思いつつgemのソースを追います。ここでかなり時間がかかりましたが、なんとか探し出したところ、gemのサーチパスはdefaults.rbというファイルの中のdefault_dirで定義されているようでした。

  def self.default_dir
    path = if defined? RUBY_FRAMEWORK_VERSION then
             :
           elsif ConfigMap[:rubylibprefix] then
             [
              {~orange:ConfigMap[:rubylibprefix],~}
              {~orange:'gems',~}
              {~orange:ConfigMap[:ruby_version]~}
             ]
           else
             :
           end

    @default_dir ||= File.join(*path)
  end

このConfigMapはrubygems.rbの中で、

  unless defined?(ConfigMap)
    ##
    # Configuration settings from ::RbConfig
    ConfigMap = Hash.new do |cm, key|
      cm[key] = RbConfig::CONFIG[key.to_s]
    end
  else
   :

のように初期化されています。RbConfig::CONFIGってなんぞやとぐぐってみると、「RbConfigというのがあってビルド時の情報とかが取れる」というブログの記事が見つかりました。こちらに習って確認すると、

% ruby19 -rpp -e"pp RbConfig::CONFIG"
{"DESTDIR"=>"",
 "MAJOR"=>"1",
 "MINOR"=>"9",
 "TEENY"=>"1",
   :
 "ruby_version"=>"1.9",
   :
 "rubylibprefix"=>"/usr/local/lib/ruby",

おぉ、ruby_versionが"1.9"だ。同じくソースを直接コンパイルした環境で試すと、

% ruby -rpp -e"pp RbConfig::CONFIG"
   :
 "ruby_version"=>"1.9.1",
   :

と、ちゃんと"1.9.1"になっています。いよいよ核心に迫ってきています。

そうすると、このruby_versionをどこかで変更しているはずだと思ってさがしてみると、/usr/ports/lang/ruby19/Makefileに、

CONFIGURE_ARGS=   ${RUBY_CONFIGURE_ARGS} \
      --enable-shared \
      --enable-pthread \
      {~orange:--with-ruby-version=minor~} \
      --with-sitedir="${PREFIX}/lib/ruby/site_ruby" \
      --with-vendordir="${PREFIX}/lib/ruby/vendor_ruby"

という記述を発見。configure --helpで確認すると、

  --with-ruby-version=STR ruby version string for version specific directories
                          [[full]] (full|minor|STR)

とあったのでビンゴ。FreeBSDのportsではこの"--with-ruby-version=minor"指定でライブラリのインストールパスを"1.9"にしておいて、最初のbsd.ruby.mkでパッケージ化する際のパスを調整しているようでした。

残る疑問は、Rubyのバージョンが"1.9.3"でもライブラリのパスがデフォルトでは"1.9.1"になっている点ですが、こちらは、1.9.2のリリースノートのFAQに答えがありました。

{''標準ライブラリが/usr/local/lib/ruby/1.9.1にインストールされる''}
    このバージョン番号は「ライブラリ互換バージョン」です。Ruby 1.9.2は1.9.1とおおよそ互換なので、ライブラリはこのディレクトリにインストールされます。

まとめると、

  • Rubyには言語のバージョンと独立したライブラリ互換バージョンという概念が存在する
  • デフォルトの状態ではライブラリ互換バージョンの値と同じ名前のディレクトリにライブラリがインストールされる
  • ライブラリのインストール先ディレクトリの名前は、configureスクリプトで"--with-ruby-version"オプションを指定する事により変更可能
  • この"--with-ruby-version"をつかってディレクトリを変更してしまうと、gemのライブラリ検索パスも変わってしまう
  • このため、例え同じバージョンのRubyであっても異なる"--with-ruby-version"オプションで構築された環境間では、{gemやbundlerのパスに関して互換性が無くなってしまう}

ということのようです。たいへん恐縮ながら、

`center large red` ライブラリの互換性を保証するために(も)使われる名前がコンパイルオプションで自由に変更できるようになってちゃ駄目なんじゃないでしょうか?

途中まではFreeBSD固有の問題かなと思っていたんですが…。

`green`