#!/usr/bin/env ruby require 'rake/baseextensiontask' # Define a series of tasks to aid in the compilation of Java extensions for # gem developer/creators. module Rake class JavaExtensionTask < BaseExtensionTask attr_accessor :classpath attr_accessor :debug # Provide source compatibility with specified release attr_accessor :source_version # Generate class files for specific VM version attr_accessor :target_version def platform @platform ||= 'java' end def java_compiling(&block) @java_compiling = block if block_given? end def init(name = nil, gem_spec = nil) super @source_pattern = '**/*.java' @classpath = nil @java_compiling = nil @debug = false @source_version = '1.5' @target_version = '1.5' end def define super define_java_platform_tasks end private def define_compile_tasks(for_platform = nil, ruby_ver = RUBY_VERSION) # platform usage platf = for_platform || platform # lib_path lib_path = lib_dir # tmp_path tmp_path = "#{@tmp_dir}/#{platf}/#{@name}" # cleanup and clobbering CLEAN.include(tmp_path) CLOBBER.include("#{lib_path}/#{binary(platf)}") CLOBBER.include("#{@tmp_dir}") # directories we need directory tmp_path directory lib_dir # copy binary from temporary location to final lib # tmp/extension_name/extension_name.{so,bundle} => lib/ task "copy:#{@name}:#{platf}" => [lib_path, "#{tmp_path}/#{binary(platf)}"] do install "#{tmp_path}/#{binary(platf)}", "#{lib_path}/#{binary(platf)}" end file "#{tmp_path}/#{binary(platf)}" => "#{tmp_path}/.build" do class_files = FileList["#{tmp_path}/**/*.class"]. gsub("#{tmp_path}/", '') # avoid environment variable expansion using backslash class_files.gsub!('$', '\$') unless windows? args = class_files.map { |path| ["-C #{tmp_path}", path] }.flatten sh "jar cf #{tmp_path}/#{binary(platf)} #{args.join(' ')}" end file "#{tmp_path}/.build" => [tmp_path] + source_files do not_jruby_compile_msg = <<-EOF WARNING: You're cross-compiling a binary extension for JRuby, but are using another interpreter. If your Java classpath or extension dir settings are not correctly detected, then either check the appropriate environment variables or execute the Rake compilation task using the JRuby interpreter. (e.g. `jruby -S rake compile:java`) EOF warn_once(not_jruby_compile_msg) unless defined?(JRUBY_VERSION) classpath_arg = java_classpath_arg(@classpath) debug_arg = @debug ? '-g' : '' sh "javac #{java_extdirs_arg} -target #{@target_version} -source #{@source_version} -Xlint:unchecked #{debug_arg} #{classpath_arg} -d #{tmp_path} #{source_files.join(' ')}" # Checkpoint file touch "#{tmp_path}/.build" end # compile tasks unless Rake::Task.task_defined?('compile') then desc "Compile all the extensions" task "compile" end # compile:name unless Rake::Task.task_defined?("compile:#{@name}") then desc "Compile #{@name}" task "compile:#{@name}" end # Allow segmented compilation by platform (open door for 'cross compile') task "compile:#{@name}:#{platf}" => ["copy:#{@name}:#{platf}"] task "compile:#{platf}" => ["compile:#{@name}:#{platf}"] # Only add this extension to the compile chain if current # platform matches the indicated one. if platf == RUBY_PLATFORM then # ensure file is always copied file "#{lib_path}/#{binary(platf)}" => ["copy:#{name}:#{platf}"] task "compile:#{@name}" => ["compile:#{@name}:#{platf}"] task "compile" => ["compile:#{platf}"] end end def define_java_platform_tasks # lib_path lib_path = lib_dir if @gem_spec && !Rake::Task.task_defined?("java:#{@gem_spec.name}") task "java:#{@gem_spec.name}" do |t| # FIXME: workaround Gem::Specification limitation around cache_file: # http://github.com/rubygems/rubygems/issues/78 spec = gem_spec.dup spec.instance_variable_set(:"@cache_file", nil) if spec.respond_to?(:cache_file) # adjust to specified platform spec.platform = Gem::Platform.new('java') # clear the extensions defined in the specs spec.extensions.clear # add the binaries that this task depends on ext_files = [] # go through native prerequisites and grab the real extension files from there t.prerequisites.each do |ext| ext_files << ext end # include the files in the gem specification spec.files += ext_files # expose gem specification for customization if @java_compiling @java_compiling.call(spec) end # Generate a package for this gem Gem::PackageTask.new(spec) do |pkg| pkg.need_zip = false pkg.need_tar = false end end # add binaries to the dependency chain task "java:#{@gem_spec.name}" => ["#{lib_path}/#{binary(platform)}"] # ensure the extension get copied unless Rake::Task.task_defined?("#{lib_path}/#{binary(platform)}") then file "#{lib_path}/#{binary(platform)}" => ["copy:#{name}:#{platform}"] end task 'java' => ["java:#{@gem_spec.name}"] end task 'java' do task 'compile' => 'compile:java' end end # # Discover Java Extension Directories and build an extdirs argument # def java_extdirs_arg extdirs = Java::java.lang.System.getProperty('java.ext.dirs') rescue nil extdirs = ENV['JAVA_EXT_DIR'] unless extdirs java_extdir = extdirs.nil? ? "" : "-extdirs \"#{extdirs}\"" end # # Discover the Java/JRuby classpath and build a classpath argument # # @params # *args:: Additional classpath arguments to append # # Copied verbatim from the ActiveRecord-JDBC project. There are a small myriad # of ways to discover the Java classpath correctly. # def java_classpath_arg(*args) jruby_cpath = nil if RUBY_PLATFORM =~ /java/ begin cpath = Java::java.lang.System.getProperty('java.class.path').split(File::PATH_SEPARATOR) cpath += Java::java.lang.System.getProperty('sun.boot.class.path').split(File::PATH_SEPARATOR) jruby_cpath = cpath.compact.join(File::PATH_SEPARATOR) rescue => e end end unless jruby_cpath jruby_cpath = ENV['JRUBY_PARENT_CLASSPATH'] || ENV['JRUBY_HOME'] && Dir.glob("#{File.expand_path(ENV['JRUBY_HOME'])}/lib/*.jar"). join(File::PATH_SEPARATOR) end raise "JRUBY_HOME or JRUBY_PARENT_CLASSPATH are not set" unless jruby_cpath jruby_cpath += File::PATH_SEPARATOR + args.join(File::PATH_SEPARATOR) unless args.empty? jruby_cpath ? "-cp \"#{jruby_cpath}\"" : "" end end end