buildrにrfscを組み込んでみた
あまりにコンパイルが遅くなってしまったので、buildrにrfsc
http://blog.8-p.info/2010/2-fsc-ruby
を組み込んでみた。
結論としては、fsc + compile引数が重要。
引数無しだとfscのキャッシュが全然効いていなかった。
compile target
> buildr compile Completed in 13.560s
テストのビルドがなくなって半分になる
compile target & fsc(fscサーバ起動済み)
> buildr compile Completed in 10.987s > buildr compile Completed in 2.882s
2回目以降fscのキャッシュが効いて爆速になるみたいだ。
キャッシュが効く条件は、同じコマンドラインが投げられることと思われる。そのため、テストが含まれていると交互に違うコマンドラインが投げられて毎回キャッシュが捨てられるのではないかと。sbtのccはこの条件をクリアしていると思う。
compile target & rfsc(fscサーバ起動済み)
> buildr compile Completed in 10.153s > buildr compile Completed in 2.253s
さらにfscクライアントの起動を節約できる。
ということで26秒から2秒に減りました。
ちゃんと、compile引数を渡していないことでfscのキャッシュ破壊が起きているのが遅さの最大の原因だったとは。
となると、fscを改造してコマンドラインをキーにして複数のキャッシュを持てる富豪仕様にするとよさげです。ソースとテストの両方をコンパイルしてもそこそこの早さでできそうだ。
buildr改造
compile.scalaへのモンキーパッチ
gems\buildr-1.4.6\lib\buildr\scala.rb
require 'buildr/scala/compiler' + require 'buildr/scala/fsc' require 'buildr/scala/tests'
gems\buildr-1.4.6\lib\buildr\scala\fsc.rb
require 'socket' require 'pathname' module Buildr::Scala class Scalac def compile(sources, target, dependencies) #:nodoc: check_options(options, OPTIONS + (Scala.version?(2.8) ? [:make] : [])) java_sources = java_sources(sources) enable_dep_tracing = Scala.version?(2.8) && java_sources.empty? dependencies.unshift target if enable_dep_tracing cmd_args = [] cmd_args << '-classpath' << dependencies.join(File::PATH_SEPARATOR) source_paths = sources.select { |source| File.directory?(source) } cmd_args << '-sourcepath' << source_paths.join(File::PATH_SEPARATOR) unless source_paths.empty? cmd_args << '-d' << File.expand_path(target) cmd_args += scalac_args if enable_dep_tracing dep_dir = File.expand_path(target) Dir.mkdir dep_dir unless File.exists? dep_dir cmd_args << '-make:' + options[:make].to_s cmd_args << '-dependencyfile' cmd_args << File.expand_path('.scala-deps', dep_dir) end cmd_args += files_from_sources(sources) unless Buildr.application.options.dryrun if Scalac.use_fsc fsc(cmd_args) # この関数の改造点はここだけ else trace((['scalac'] + cmd_args).join(' ')) Java.load begin Java.scala.tools.nsc.Main.process(cmd_args.to_java(Java.java.lang.String)) rescue => e fail "Scala compiler crashed:\n#{e.inspect}" end fail 'Failed to compile, see errors above' if Java.scala.tools.nsc.Main.reporter.hasErrors end unless java_sources.empty? trace 'Compiling mixed Java/Scala sources' # TODO includes scala-compiler.jar deps = dependencies + Scalac.dependencies + [ File.expand_path(target) ] @java.compile(java_sources, target, deps) end end end # 以下、fsc用追加関数 def find_tmp_dir path=Pathname.new(`which scala`.chomp).dirname + '../var/scala-devel' if path.exist? then return path end path=Pathname.new(ENV['TEMP']+'/scala-devel/'+ENV['USERNAME']) if path.exist? then return path end throw "no port directory" end def find_port(dir) ports = dir.entries.map{|e|e.to_s}.grep(/^\d+$/) raise "Failed to find port file." if ports.empty? return ports[0], (dir + ports[0]).read.chomp end def open_socket port, password = find_port(find_tmp_dir + 'scalac-compile-server-port') socket = TCPSocket.open('localhost', port) socket.puts(password) return socket end def transform_argv(argv) dir = if argv.include?('-d') [] else ['-d', '.'] end (dir + argv).map do |i| case i when /^-/, /;/ i else Pathname.new(i).realpath end end end def fsc(cmd_args) #cmd_args << "-verbose" argv = cmd_args begin socket = open_socket trace((['rfsc'] + cmd_args).join(' ')) socket.puts argv.join("\0") print socket.read true rescue => e p e #cmd_args << "-JXmx512M" trace((['fsc'] + cmd_args).join(' ')) system(([File.expand_path('bin/fsc', Scalac.scala_home)] + cmd_args).join(' ')) or fail 'Failed to compile, see errors above' end end end end