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 をいじるやり方のほうが筋がよさそう。