You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@buildr.apache.org by vb...@apache.org on 2008/03/05 20:28:15 UTC

svn commit: r633989 - /incubator/buildr/trunk/lib/java/nailgun.rb

Author: vborja
Date: Wed Mar  5 11:28:13 2008
New Revision: 633989

URL: http://svn.apache.org/viewvc?rev=633989&view=rev
Log:
basic Nailgun integration.

Implemented a nailgun server, client, runtimePool.
Currently we are able to run tasks on a loaded buildr environment

Added:
    incubator/buildr/trunk/lib/java/nailgun.rb

Added: incubator/buildr/trunk/lib/java/nailgun.rb
URL: http://svn.apache.org/viewvc/incubator/buildr/trunk/lib/java/nailgun.rb?rev=633989&view=auto
==============================================================================
--- incubator/buildr/trunk/lib/java/nailgun.rb (added)
+++ incubator/buildr/trunk/lib/java/nailgun.rb Wed Mar  5 11:28:13 2008
@@ -0,0 +1,355 @@
+module Buildr
+  
+  # To start the nailgun server run the ng:start task, this will
+  # download, compile and installed nailgun if needed, afterwards
+  # it will start the nailgun server.
+  module Nailgun 
+    VERSION = '0.7.1'
+    NAME = "nailgun-#{VERSION}"
+    URL = "http://downloads.sourceforge.net/nailgun/#{NAME}.zip"
+    ARTIFACT_SPEC = "com.martiansoftware:nailgun:jar:#{VERSION}"
+
+    class << self
+      attr_accessor :artifact
+      attr_accessor :iface, :port, :runtime_pool_size
+      attr_accessor :jruby_home, :home
+    end
+
+    self.jruby_home = if PLATFORM =~ /java/
+                        Config::CONFIG['prefix']
+                      else
+                        ENV['JRUBY_HOME'] || File.join(ENV['HOME'], '.jruby')
+                      end
+    self.home = ENV['NAILGUN_HOME'] || File.join(jruby_home, 'tool', 'nailgun')
+    self.iface = 'localhost'
+    self.port = 2113
+    self.runtime_pool_size = 0
+        
+    namespace :nailgun do
+      tmp = lambda { |*files| File.join(Dir.tmpdir, "nailgun", *files) }
+      dist_zip = Buildr.download(tmp[NAME + ".zip"] => URL)
+      dist_dir = Buildr.unzip(tmp[NAME] => dist_zip)
+      
+      if File.exist?(File.join(home, NAME + ".jar"))
+        ng_jar = file(File.join(home, NAME + ".jar"))
+      else
+        ng_jar = file(tmp[NAME, NAME, NAME+".jar"] => dist_dir)
+      end
+      
+      self.artifact = Buildr.artifact(ARTIFACT_SPEC).from(ng_jar)
+      
+      compiled_bin = tmp[NAME, NAME, 'ng']
+      compiled_bin << '.exe' if Config::CONFIG['host_os'] =~ /mswin/i
+      compiled_bin = file(compiled_bin => dist_dir.target) do |task|
+        unless task.to_s.pathmap('%x') == '.exe'
+          Dir.chdir(task.to_s.pathmap('%d')) do
+            puts "Compiling #{task.to_s}"
+            system('make', task.to_s.pathmap('%f')) or
+              fail "Nailgun binary compilation failed."
+          end
+        end
+      end
+
+      installed_bin = file(File.join(home, 
+          compiled_bin.to_s.pathmap('%f')) => compiled_bin) do |task|
+        mkpath task.to_s.pathmap('%d'), :verbose => false
+        cp compiled_bin.to_s, task.to_s, :verbose => false
+      end
+
+      task :boot => artifact do
+        if $nailgun_server
+          raise "Already nunning on Nailgun server: #{$nailgun_server}"
+        end
+        BOOT.call
+      end
+      
+      task :start => [installed_bin, :boot] do
+        factory = BuildrFactory.new(runtime_pool_size)
+        $nailgun_server = BuildrServer.new(iface, port, factory)
+        puts "Starting #{$nailgun_server}"
+        $nailgun_server.start_server
+      end
+    end # namespace :nailgun
+
+    
+  end # module Nailgun
+
+  Nailgun::BOOT = lambda do
+    require 'jruby'
+    
+    class Application
+      def buildfile(dir = nil, candidates = nil)
+        Nailgun::Util.find_buildfile(dir || Dir.pwd, candidates || @rakefiles)
+      end
+
+      def clear_invoked
+        tasks.each { |t| t.instance_variable_set(:@already_invoked, false) }
+      end
+    end
+
+    module Nailgun
+      class ::ConcreteJavaProxy
+        def self.jclass(name = nil)
+          name ||= self.java_class.name
+          Util.class_for_name(name)
+        end
+        
+        def self.jnew(*args)
+          objs = []
+          classes = args.map do |a|
+            case a
+            when nil
+              obj << nil
+              nil
+            when Hash
+              objs << a.keys.first
+              cls = a.values.first
+              cls = Util.proxy_class(cls) if String == cls
+              cls
+            else
+              objs << a
+              a.java_class
+            end
+          end
+          classes = classes.to_java(java.lang.Class)
+          ctor = jclass.getDeclaredConstructor(classes)
+          ctor.setAccessible(true)
+          ctor.newInstance(objs.to_java(java.lang.Object))
+        end
+      end
+      
+      module Util
+        extend self
+
+        def class_for_name(name)
+          java.lang.Class.forName(name)
+        end
+        
+        def add_to_sysloader(path)
+          sysloader = java.lang.ClassLoader.system_class_loader
+          add_url_method = class_for_name('java.net.URLClassLoader').
+            getDeclaredMethod('addURL', [java.net.URL].to_java(java.lang.Class))
+          add_url_method.accessible = true
+          add_url_method.invoke(sysloader, [java.io.File.new(path.to_s).
+                                            toURL].to_java(java.net.URL))
+        end
+        add_to_sysloader Nailgun.artifact
+                
+        def proxy_class(name)
+          JavaUtilities.get_proxy_class(name)
+        end
+
+        def find_buildfile(pwd, candidates)
+          buildfile = candidates.find { |c| File.file?(File.join(pwd, c)) }
+          return File.expand_path(buildfile, pwd) if buildfile
+          updir = File.dirname(pwd)
+          return nil if File.expand_path(updir) == File.expand_path(pwd)
+          find_buildfile(updir, candidates)
+        end
+
+        def benchmark(action = 'Completed', verbose = true)
+          result = nil
+          times = Benchmark.measure do
+            result = yield
+          end
+          if verbose
+            real = []
+            real << ("%ih" % (times.real / 3600)) if times.real >= 3600
+            real << ("%im" % ((times.real / 60) % 60)) if times.real >= 60
+            real << ("%.3fs" % (times.real % 60))
+            puts "#{action} in #{real.join}"
+          end
+          result
+        end
+
+        import org.jruby.RubyIO 
+        def redirect_stdio(runtime, nail)
+          result = nil
+          stdin  = runtime.global_variables.get('$stdin')
+          stdout = runtime.global_variables.get('$stdout')
+          stderr = runtime.global_variables.get('$stderr')
+
+          set_in = lambda do |i|
+            runtime.global_variables.set('$stdin', i)
+            runtime.object.send(:remove_const, 'STDIN')
+            runtime.object.send(:const_set, 'STDIN', i)
+          end
+          
+          begin
+            p nail.in
+            input  = RubyIO.jnew(runtime, java.lang.System.in => java.io.InputStream)
+            output = RubyIO.jnew(runtime, nail.out => java.io.OutputStream)
+            error = RubyIO.jnew(runtime, nail.err => java.io.OutputStream)
+            #stdin.reopen(input, 'r') # not working on jruby :(
+            #set_in.call(input)
+            stdout.reopen(output)
+            stderr.reopen(error)
+            result = yield
+          ensure
+            input  = RubyIO.jnew(runtime, java.lang.System.in => java.io.InputStream)
+            output = RubyIO.jnew(runtime, java.lang.System.out => java.io.OutputStream)
+            error = RubyIO.jnew(runtime, java.lang.System.err => java.io.OutputStream)
+            #set_in.call(input)
+            stdout.reopen(output)
+            stderr.reopen(error)
+          end
+          result
+        end
+        
+      end
+
+      class BuildrNail 
+        include org.apache.buildr.BuildrNail
+        Main = Util.proxy_class 'org.apache.buildr.BuildrNail$Main'
+        
+        attr_reader :buildfile
+        
+        def initialize
+          @buildfile = Rake.application.buildfile
+          @runtimes = { @buildfile => JRuby.runtime }
+        end
+
+        def run(nail)
+          nail.assert_loopback_client
+          nail.out.println "Obtaining Buildr runtime from #{nail.getNGServer}"
+          pwd = nail.working_directory
+          env = nail.env
+          argv = [nail.command] + nail.args.map(&:to_s)
+
+          ARGV.replace(argv)
+          Util.redirect_stdio(JRuby.runtime, nail) do
+            Util.benchmark 'Local Buildr completed' do 
+              Rake.application.instance_eval do
+                clear_invoked
+                opts = GetoptLong.new(*command_line_options)
+                opts.each { |opt, value| do_option(opt, value) }
+                collect_tasks
+                @top_level_tasks.delete('buildr:initialize')
+                top_level
+              end
+            end
+          end
+          
+        end
+
+        private
+
+      end # class BuildrNail
+
+      class BuildrFactory
+        require 'thread'
+        require 'monitor'
+        
+        attr_reader :work_queue_size
+
+        def initialize(size)
+          @work_queue = [].extend(MonitorMixin)
+          @work_queue_size = size
+          @ready_cond = @work_queue.new_cond
+          @workers = [].extend(MonitorMixin)
+        end
+
+        def obtain
+          @work_queue.synchronize do 
+            @ready_cond.wait_while { @work_queue.empty? }
+            @work_queue.shift
+          end
+        end
+
+        def start
+          puts "Starting Buildr runtime queue"
+          @thread = Thread.new do
+            create_if_needed
+            sleep 10
+            loop { create_if_needed }
+          end
+        end
+
+        def stop
+          @thread.kill if @thread
+        end
+
+        private
+        def configure_runtime(runtime)
+          load_service = runtime.getLoadService
+          load_service.add_path File.expand_path('..', File.dirname(__FILE__))
+          load_service.require 'rubygems'
+          load_service.require 'buildr'
+        end
+
+        def create_runtime
+          @workers.synchronize { @workers << Thread.current }
+          puts "Creating new Buildr runtime"
+          cfg = org.jruby.RubyInstanceConfig.new
+          cfg.input = java.lang.System.in
+          cfg.output = java.lang.System.out
+          cfg.error = java.lang.System.err
+          cfg.current_directory Dir.pwd
+          runtime = nil
+          times = Benchmark.measure do 
+            runtime = org.jruby.Ruby.new_instance(cfg)
+            configure_runtime(runtime)
+          end
+          real = []
+          real << ("%ih" % (times.real / 3600)) if times.real >= 3600
+          real << ("%im" % ((times.real / 60) % 60)) if times.real >= 60
+          real << ("%.3fs" % (times.real % 60))
+          puts "Buildr runtime #{runtime} created in #{real.join}"
+
+          @workers.synchronize do 
+            @workers.delete(Thread.current)
+            @work_queue.synchronize { @work_queue << runtime }
+          end
+          
+          create_if_needed
+          @ready_cond.signal
+        end
+
+        def create_if_needed
+          return unless may_create?
+          Thread.new { create_runtime }
+        end
+        
+        def may_create?
+          @workers.synchronize do
+            workers = @workers.size
+            worked = @work_queue.synchronize { @work_queue.size }
+            (workers + worked) < work_queue_size
+          end
+        end
+      end # BuildrFactory
+
+      class BuildrServer < com.martiansoftware.nailgun.NGServer
+        attr_reader :buildr_factory
+
+        def initialize(host = 'localhost', port = 2113, buildr_factory = nil)
+          super(java.net.InetAddress.get_by_name(host), port)
+          @buildr_factory = buildr_factory
+          @host, @port = host, port
+        end
+        
+        def start_server
+          self.allow_nails_by_class_name = false
+
+          BuildrNail::Main.nail = BuildrNail.new
+          self.default_nail_class = BuildrNail::Main
+          buildr_factory.start
+          
+          t = java.lang.Thread.new(self)
+          t.setName(to_s)
+          t.start
+          
+          sleep 1 while getPort == 0
+          puts "#{self} Started."
+        end
+
+        def to_s
+          "BuildrServer(" <<
+            [Rake.application.buildfile, @host, @port].join(", ") <<
+            ")"
+        end
+      end # class BuildrServer
+      
+    end # module Nailgun
+  end # Nailgun::Boot
+end