bin/gems に pry を仕込んでステップ実行。コマンドは以下のように実行してみた。

LDFLAGS="`mysql_config --libs` -lstdc++" bin/gem install mysql2 -- --with-mysql-lib=/usr/lib64/mysql
  1 #!/usr/bin/env ruby
  2 #--
  3 # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
  4 # All rights reserved.
  5 # See LICENSE.txt for permissions.
  6 #++
  7
  8 require 'rubygems'
  9 require 'rubygems/gem_runner'
 10 require 'rubygems/exceptions'
 11
 12 required_version = Gem::Requirement.new ">= 1.8.7"
 13
 14 unless required_version.satisfied_by? Gem.ruby_version then
 15   abort "Expected Ruby Version #{required_version}, is #{Gem.ruby_version}"
 16 end
 17
 18 args = ARGV.clone
 19
 20 begin
=> 21   binding,pry
 22   Gem::GemRunner.new.run args
 23 rescue Gem::SystemExitException => e
 24   exit e.exit_code
 25 end
 26

中略

この辺りからC拡張の処理っぽいところに入ってきた

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/installer.rb @ line 230 Gem::Installer#install:

    225:
    226:     if @options[:install_as_default]
    227:       extract_bin
    228:       write_default_spec
    229:     else
    230:       extract_files
    231:
 => 232:       build_extensions
    233:       write_build_info_file
    234:       run_post_build_hooks
    235:
From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/installer.rb @ line 675 Gem::Installer#build_extensions:

    674: def build_extensions
 => 675:   builder = Gem::Ext::Builder.new spec, @build_args
    676:
    677:   builder.build_extensions
    678: end

ここで ruby ext/mysql2/extconf.rb を実行して、Makefile を生成しているようだ。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 198 Gem::Ext::Builder#build_extensions:

    179: def build_extensions
    180:   return if @spec.extensions.empty?
    181:
    182:   if @build_args.empty?
    183:     say "Building native extensions.  This could take a while..."
    184:   else
    185:     say "Building native extensions with: '#{@build_args.join ' '}'"
    186:     say "This could take a while..."
    187:   end
    188:
    189:   dest_path = @spec.extension_dir
    190:
    191:   FileUtils.rm_f @spec.gem_build_complete_path
    192:
    193:   @ran_rake = false # only run rake once
    194:
    195:   @spec.extensions.each do |extension|
    196:     break if @ran_rake
    197:
 => 198:     build_extension extension, dest_path
    199:   end
    200:
    201:   FileUtils.touch @spec.gem_build_complete_path
    202: end

[1] pry(#<Gem::Ext::Builder>)> extension
=> "ext/mysql2/extconf.rb"
[2] pry(#<Gem::Ext::Builder>)> dest_path
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/extensions/x86-linux/2.1.0-static/mysql2-0.3.16"

gem install mysql -- --with-mysql-lib=/usr/lib64/mysql のように渡したオプションは@build_args に渡っている。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 161 Gem::Ext::Builder#build_extension:

    146: def build_extension extension, dest_path # :nodoc:
    147:   results = []
    148:
    149:   extension ||= '' # I wish I knew why this line existed
    150:   extension_dir =
    151:     File.expand_path File.join @gem_dir, File.dirname(extension)
    152:   lib_dir = File.join @spec.full_gem_path, @spec.raw_require_paths.first
    153:
    154:   builder = builder_for extension
    155:
    156:   begin
    157:     FileUtils.mkdir_p dest_path
    158:
    159:     CHDIR_MUTEX.synchronize do
    160:       Dir.chdir extension_dir do
 => 161:         results = builder.build(extension, @gem_dir, dest_path,
    162:                                 results, @build_args, lib_dir)
    163:
    164:         say results.join("\n") if Gem.configuration.really_verbose
    165:       end
    166:     end
    167:
    168:     write_gem_make_out results.join "\n"
    169:   rescue => e
    170:     results << e.message
    171:     build_error extension_dir, results.join("\n"), $@
    172:   end
    173: end

[4] pry(#<Gem::Ext::Builder>)> extension
=> "ext/mysql2/extconf.rb"
[5] pry(#<Gem::Ext::Builder>)> @gem_dir
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16"
[6] pry(#<Gem::Ext::Builder>)> dest_path
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/extensions/x86-linux/2.1.0-static/mysql2-0.3.16"
[7] pry(#<Gem::Ext::Builder>)> results
=> []
[8] pry(#<Gem::Ext::Builder>)> @build_args
=> ["--with-mysql-lib=/usr/lib64/mysql"]
[9] pry(#<Gem::Ext::Builder>)> lib_dir
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib"

結果、最終的に ruby extconf.rb --with-mysql-lib=/usr/lib64/mysql と実行されて、Makefile が生成されている。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/ext_conf_builder.rb @ line 39 Gem::Ext::ExtConfBuilder.build:

    34:       begin
    35:         ENV["RUBYOPT"] = ["-r#{siteconf_path}", rubyopt].compact.join(' ')
    36:         cmd = [Gem.ruby, File.basename(extension), *args].join ' '
    37:
    38:         begin
 => 39:           run cmd, results
    40:         ensure
    41:           FileUtils.mv 'mkmf.log', dest_path if File.exist? 'mkmf.log'
    42:         end
    43:
    44:         ENV["DESTDIR"] = nil

[1] pry(Gem::Ext::ExtConfBuilder)> cmd
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/bin/ruby extconf.rb --with-mysql-lib=/usr/lib64/mysql"
From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 72 Gem::Ext::Builder.run:

    67:       rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil
    68:       if verbose
    69:         puts(command)
    70:         system(command)
    71:       else
 => 72:         results << command
    73:         results << `#{command} #{redirector}`
    74:       end
    75:     ensure
    76:       ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps
    77:     end

[1] pry(Gem::Ext::ExtConfBuilder)> command
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/bin/ruby extconf.rb --with-mysql-lib=/usr/lib64/mysql"
[2] pry(Gem::Ext::ExtConfBuilder)> redirector
=> "2>&1"

make の箇所

その後は make コマンドの実行を行っているようだ。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/ext_conf_builder.rb @ line 48 Gem::Ext::ExtConfBuilder.build:

    43:
    44:         ENV["DESTDIR"] = nil
    45:         ENV["RUBYOPT"] = rubyopt
    46:         siteconf.unlink
    47:
 => 48:         make dest_path, results
    49:
    50:         if tmp_dest
    51:           # TODO remove in RubyGems 3
    52:           if Gem.install_extension_in_lib and lib_dir then
    53:             FileUtils.mkdir_p lib_dir

[1] pry(Gem::Ext::ExtConfBuilder)> dest_path
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/extensions/x86-linux/2.1.0-static/mysql2-0.3.16"

あまり明確に意識していなかったが、make clean してから、make して、make install していた。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 51 Gem::Ext::Builder.make:

    29: def self.make(dest_path, results)
    30:   unless File.exist? 'Makefile' then
    31:     raise Gem::InstallError, 'Makefile not found'
    32:   end
    33:
    34:   # try to find make program from Ruby configure arguments first
    35:   RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
    36:   make_program = ENV['MAKE'] || ENV['make'] || $1
    37:   unless make_program then
    38:     make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
    39:   end
    40:
    41:   destdir = '"DESTDIR=%s"' % ENV['DESTDIR'] if RUBY_VERSION > '2.0'
    42:
    43:   ['clean', '', 'install'].each do |target|
    44:     # Pass DESTDIR via command line to override what's in MAKEFLAGS
    45:     cmd = [
    46:       make_program,
    47:       destdir,
    48:       target
    49:     ].join(' ').rstrip
    50:     begin
 => 51:       run(cmd, results, "make #{target}".rstrip)
    52:     rescue Gem::InstallError
    53:       raise unless target == 'clean' # ignore clean failure
    54:     end
    55:   end
    56: end

extconf.rb 実行時と同じ run メソッドに入って、make コマンドが実行されている。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 73 Gem::Ext::Builder.run:

    68:       rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil
    69:       if verbose
    70:         puts(command)
    71:         system(command)
    72:       else
 => 73:         results << command
    74:         results << `#{command} #{redirector}`
    75:       end
    76:     ensure
    77:       ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps
    78:     end

[1] pry(Gem::Ext::ExtConfBuilder)> command
=> "make \"DESTDIR=\""

make にオプションを渡す方法

今回、make 時に -lstdc++ を指定したかったので、LDFLAGS 環境変数を指定してみたがうまくいかなかった。

調査してみると、ruby ext/mysql2/extconf.rb で生成した Makefile 内ではそもそも LDFLAGS という値を使っていなかった。(ldflags ならあった。うーん、LDFLAGS のほうが一般的だと思うが ...)

# Makefile

LOCAL_LIBS =
LIBS =  -L/usr/lib64 -lmysqlclient -lpthread -lm -lrt -ldl  -lpthread -lrt -ldl -lcrypt -lm   -lc

...

$(DLLIB): $(OBJS) Makefile
        $(ECHO) linking shared-object mysql2/$(DLLIB)
        -$(Q)$(RM) $(@)
        $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)

代わりに LOCAL_LIBS というよさげなやつを見つけたので、

$ LOCAL_LIBS="-lstdc++" make

とやってみたが、これはダメだった。これは make の話だけども、どうやら環境変数で指定する場合は make -e オプションを使わないと Makefile 内の設定で上書きされてしまうらしい。もしくは、

$ make LOCAL_LIBS="-lstdc++"

のように書くと、make の機能として変数置換をしてくれるようだ。make -e は環境変数全てを 変数設定してしまうため、こちらの方式のほうが良さそうだ。

さて、コードリーディングの結果 make コマンドは、このような定義になっていて、

make_program = ENV['MAKE'] || ENV['make'] || $1
unless make_program then
  make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end

MAKE 環境変数で制御できるようだ。そこで、以下のように MAKE 環境変数に引数を指定してみたところ、-lstdc++ へのリンクも追加してビルドできた。

MAKE="make LOCAL_LIBS='-lstdc++'" gem install mysql2 -- --with-mysql-lib=/usr/lib64/mysql

というわけで、MAKE 環境変数に指定することで、make にオプションを渡せる。

補足:とはいえ、bundle install 時に環境変数で渡すとなると全 gem に影響が出てしまうので、あまり使い勝手はよくなさそうだ。やはり、mysql2 gem に関しては mysql2 gem を mysql5.6 の libmysqlclient.a と static link したい話 に書いたように /usr/lib/mysql_config をいじるやり方のほうが筋がよさそう。