lib/rake/extensiontask.rb in luislavena-rake-compiler-0.1.1 vs lib/rake/extensiontask.rb in luislavena-rake-compiler-0.2.0

- old
+ new

@@ -7,20 +7,26 @@ require 'rake/clean' require 'rake/tasklib' require 'rbconfig' module Rake + autoload :GemPackageTask, 'rake/gempackagetask' + autoload :YAML, 'yaml' + class ExtensionTask < TaskLib attr_accessor :name attr_accessor :gem_spec attr_accessor :config_script attr_accessor :tmp_dir attr_accessor :ext_dir attr_accessor :lib_dir attr_accessor :platform - attr_accessor :additional_options + attr_accessor :config_options attr_accessor :source_pattern + attr_accessor :cross_compile + attr_accessor :cross_platform + attr_accessor :cross_config_options def initialize(name = nil, gem_spec = nil) init(name, gem_spec) yield self if block_given? define @@ -32,151 +38,233 @@ @config_script = 'extconf.rb' @tmp_dir = 'tmp' @ext_dir = 'ext' @lib_dir = 'lib' @source_pattern = "*.c" - @additional_options = [] + @config_options = [] + @cross_compile = false + @cross_config_options = [] end def platform @platform ||= RUBY_PLATFORM end + def cross_platform + @cross_platform ||= 'i386-mingw32' + end + def define fail "Extension name must be provided." if @name.nil? define_compile_tasks - define_native_tasks if @gem_spec + + # only gems with 'ruby' platforms are allowed to define native tasks + define_native_tasks if @gem_spec && @gem_spec.platform == 'ruby' + + # only define cross platform functionality when enabled + # FIXME: there is no value for having this on Windows or JRuby + define_cross_platform_tasks if @cross_compile end private - def define_compile_tasks + def define_compile_tasks(for_platform = nil) + # platform usage + platf = for_platform || platform + + # tmp_path + tmp_path = "#{@tmp_dir}/#{platf}/#{@name}" + + # cleanup and clobbering + CLEAN.include(tmp_path) + CLOBBER.include("#{@lib_dir}/#{binary}") + CLOBBER.include("#{@tmp_dir}") + # directories we need directory tmp_path - directory @lib_dir + directory lib_dir - # platform specific temp folder should be on the cleaning - CLEAN.include(tmp_path) + # copy binary from temporary location to final lib + # tmp/extension_name/extension_name.{so,bundle} => lib/ + task "copy:#{@name}:#{platf}" => [lib_dir, "#{tmp_path}/#{binary}"] do + cp "#{tmp_path}/#{binary}", "#{@lib_dir}/#{binary}" + end + # binary in temporary folder depends on makefile and source files + # tmp/extension_name/extension_name.{so,bundle} + file "#{tmp_path}/#{binary}" => ["#{tmp_path}/Makefile"] + source_files do + chdir tmp_path do + sh make + end + end + # makefile depends of tmp_dir and config_script # tmp/extension_name/Makefile - file makefile => [tmp_path, extconf] do + file "#{tmp_path}/Makefile" => [tmp_path, extconf] do |t| + options = @config_options.dup + + # rbconfig.rb will be present if we are cross compiling + if t.prerequisites.include?("#{tmp_path}/rbconfig.rb") then + options.push(*@cross_config_options) + end + parent = Dir.pwd - Dir.chdir tmp_path do + chdir tmp_path do # FIXME: Rake is broken for multiple arguments system() calls. # Add current directory to the search path of Ruby # Also, include additional parameters supplied. - ruby ['-I.', File.join(parent, extconf), *@additional_options].join(' ') + ruby ['-I.', File.join(parent, extconf), *options].join(' ') end end - # binary in temporary folder depends on makefile and source files - # tmp/extension_name/extension_name.{so,bundle} - file tmp_binary => [makefile] + source_files do - Dir.chdir tmp_path do - sh make - end + # compile tasks + unless Rake::Task.task_defined?('compile') then + desc "Compile all the extensions" + task "compile" end - # copy binary from temporary location to final lib - # tmp/extension_name/extension_name.{so,bundle} => lib/ - file lib_binary => [lib_path, tmp_binary] do - cp tmp_binary, lib_binary + # compile:name + unless Rake::Task.task_defined?("compile:#{@name}") then + desc "Compile #{@name}" + task "compile:#{@name}" end - # clobbering should remove the binaries from lib_path - CLOBBER.include(lib_binary) + # Allow segmented compilation by platfrom (open door for 'cross compile') + task "compile:#{@name}:#{platf}" => ["copy:#{@name}:#{platf}"] + task "compile:#{platf}" => ["compile:#{@name}:#{platf}"] - # we should also clobber the tmp folder - CLOBBER.include(@tmp_dir) + # 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_dir}/#{binary}" => ["copy:#{name}:#{platf}"] - desc "Compile just the #{@name} extension" - task "compile:#{@name}" => [lib_binary] - - desc "Compile the extension(s)" unless Rake::Task.task_defined?('compile') - task "compile" => ["compile:#{@name}"] + task "compile:#{@name}" => ["compile:#{@name}:#{platf}"] + task "compile" => ["compile:#{platf}"] + end end - def define_native_tasks - # only gems with 'ruby' platforms are allowed to define native tasks - return unless @gem_spec.platform == 'ruby' + def define_native_tasks(for_platform = nil) + platf = for_platform || platform - require 'rake/gempackagetask' unless defined?(Rake::GemPackageTask) + # tmp_path + tmp_path = "#{@tmp_dir}/#{platf}/#{@name}" # create 'native:gem_name' and chain it to 'native' task - native_task_for(@gem_spec) + spec = @gem_spec.dup - # hook the binary to the prerequisites for this task - task native_task_gem => [lib_binary] - end + unless Rake::Task.task_defined?("native:#{@gem_spec.name}:#{platf}") + task "native:#{@gem_spec.name}:#{platf}" do |t| + # adjust to specified platform + spec.platform = platf - def makefile - "#{tmp_path}/Makefile" - end + # clear the extensions defined in the specs + spec.extensions.clear - def extconf - "#{ext_path}/#{@config_script}" - end + # add the binaries that this task depends on + # ensure the files get properly copied to lib_dir + ext_files = t.prerequisites.map { |ext| "#{@lib_dir}/#{File.basename(ext)}" } + ext_files.each do |ext| + unless Rake::Task.task_defined?("#{@lib_dir}/#{File.basename(ext)}") then + # strip out path and .so/.bundle + file "#{@lib_dir}/#{File.basename(ext)}" => ["copy:#{File.basename(ext).ext('')}:#{platf}"] + end + end - def tmp_path - File.join(@tmp_dir, platform, @name) - end + # include the files in the gem specification + spec.files += ext_files - def ext_path - File.join(@ext_dir, @name) - end + # Generate a package for this gem + gem_package = Rake::GemPackageTask.new(spec) do |pkg| + pkg.need_zip = false + pkg.need_tar = false + end - def lib_path - @lib_dir - end + # ensure the binaries are copied + task "#{gem_package.package_dir}/#{gem_package.gem_file}" => ["copy:#{@name}:#{platf}"] + end + end - def make - RUBY_PLATFORM =~ /mswin/ ? 'nmake' : 'make' - end + # add binaries to the dependency chain + task "native:#{@gem_spec.name}:#{platf}" => ["#{tmp_path}/#{binary}"] - def binary - "#{@name}.#{RbConfig::CONFIG['DLEXT']}" - end + # Allow segmented packaging by platfrom (open door for 'cross compile') + task "native:#{platf}" => ["native:#{@gem_spec.name}:#{platf}"] - def source_files - @source_files ||= FileList["#{ext_path}/#{@source_pattern}"] + # Only add this extension to the compile chain if current + # platform matches the indicated one. + if platf == RUBY_PLATFORM then + task "native:#{@gem_spec.name}" => ["native:#{@gem_spec.name}:#{platf}"] + task "native" => ["native:#{platf}"] + end end - def tmp_binary - "#{tmp_path}/#{binary}" - end + def define_cross_platform_tasks + config_path = File.expand_path("~/.rake-compiler/config.yml") + major_ver = RUBY_VERSION.match(/(\d+.\d+)/)[1] - def lib_binary - "#{lib_path}/#{binary}" - end + # warn the user about the need of configuration to use cross compilation. + unless File.exist?(config_path) + warn "rake-compiler must be configured first to enable cross-compilation" + return + end - def native_task_gem - "native:#{@gem_spec.name}" - end + config_file = YAML.load_file(config_path) - def native_task_for(gem_spec) - return if Rake::Task.task_defined?(native_task_gem) + # tmp_path + tmp_path = "#{@tmp_dir}/#{cross_platform}/#{@name}" - spec = gem_spec.dup + unless rbconfig_file = config_file["rbconfig-#{major_ver}"] then + fail "no configuration section for this version of Ruby (rbconfig-#{major_ver})" + end - task native_task_gem do |t| - # adjust to current platform - spec.platform = (@platform || Gem::Platform::CURRENT) + # define compilation tasks for cross platfrom! + define_compile_tasks(cross_platform) - # clear the extensions defined in the specs - spec.extensions.clear + # chain rbconfig.rb to Makefile generation + file "#{tmp_path}/Makefile" => ["#{tmp_path}/rbconfig.rb"] - # add the binary dependencies of this task - spec.files += t.prerequisites + # copy the file from the cross-ruby location + file "#{tmp_path}/rbconfig.rb" => [rbconfig_file] do |t| + cp t.prerequisites.first, t.name + end - # Generate a package for this gem - gem_package = Rake::GemPackageTask.new(spec) do |pkg| - pkg.need_zip = false - pkg.need_tar = false + # now define native tasks for cross compiled files + define_native_tasks(cross_platform) if @gem_spec && @gem_spec.platform == 'ruby' + + # create cross task + task 'cross' do + # clear compile dependencies + Rake::Task['compile'].prerequisites.clear + + # chain the cross platform ones + task 'compile' => ["compile:#{cross_platform}"] + + # clear lib/binary dependencies and trigger cross platform ones + Rake::Task["#{@lib_dir}/#{binary}"].prerequisites.clear + file "#{@lib_dir}/#{binary}" => ["copy:#{@name}:#{cross_platform}"] + + # if everything for native task is in place + if @gem_spec && @gem_spec.platform == 'ruby' then + Rake::Task['native'].prerequisites.clear + task 'native' => ["native:#{cross_platform}"] end end + end - # add this native task to the list - task "native" => [native_task_gem] + def extconf + "#{@ext_dir}/#{@name}/#{@config_script}" + end + + def make + RUBY_PLATFORM =~ /mswin/ ? 'nmake' : 'make' + end + + def binary + "#{@name}.#{RbConfig::CONFIG['DLEXT']}" + end + + def source_files + @source_files ||= FileList["#{@ext_dir}/#{@name}/#{@source_pattern}"] end end end