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