require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') require 'rake/extensiontask' require 'rbconfig' require 'yaml' describe Rake::ExtensionTask do context '#new' do context '(basic)' do it 'should raise an error if no name is provided' do lambda { Rake::ExtensionTask.new }.should raise_error(RuntimeError, /Extension name must be provided/) end it 'should allow string as extension name assignation' do ext = Rake::ExtensionTask.new('extension_one') ext.name.should == 'extension_one' end it 'should allow symbol as extension name assignation' do ext = Rake::ExtensionTask.new(:extension_one) ext.name.should == 'extension_one' end it 'should allow string as extension name using block assignation' do ext = Rake::ExtensionTask.new do |ext| ext.name = 'extension_two' end ext.name.should == 'extension_two' end it 'should return itself for the block' do from_block = nil from_lasgn = Rake::ExtensionTask.new('extension_three') do |ext| from_block = ext end from_block.should == from_lasgn end it 'should accept a gem specification as parameter' do spec = mock_gem_spec ext = Rake::ExtensionTask.new('extension_three', spec) ext.gem_spec.should == spec end it 'should allow gem specification be defined using block assignation' do spec = mock_gem_spec ext = Rake::ExtensionTask.new('extension_four') do |ext| ext.gem_spec = spec end ext.gem_spec.should == spec end it 'should allow forcing of platform' do ext = Rake::ExtensionTask.new('weird_extension') do |ext| ext.platform = 'universal-foo-bar-10.5' end ext.platform.should == 'universal-foo-bar-10.5' end end end context '(defaults)' do before :each do @ext = Rake::ExtensionTask.new('extension_one') end it 'should look for extconf script' do @ext.config_script.should == 'extconf.rb' end it 'should dump intermediate files to tmp/' do @ext.tmp_dir.should == 'tmp' end it 'should copy build extension into lib/' do @ext.lib_dir.should == 'lib' end it 'should look for C/C++ files pattern (.c,.cc,.cpp)' do @ext.source_pattern.should == "*.{c,cc,cpp}" end it 'should have no configuration options preset to delegate' do @ext.config_options.should be_empty end it "should have no includes preset to delegate" do @ext.config_includes.should be_empty end it 'should default to current platform' do @ext.platform.should == RUBY_PLATFORM end it 'should default to no cross compilation' do @ext.cross_compile.should == false end it 'should have no configuration options for cross compilation' do @ext.cross_config_options.should be_empty end it "should have cross platform defined to 'i386-mingw32'" do @ext.cross_platform.should == 'i386-mingw32' end end context '(tasks)' do before :each do Rake.application.clear CLEAN.clear CLOBBER.clear end context '(one extension)' do before :each do allow(Rake::FileList).to receive(:[]).and_return(["ext/extension_one/source.c"], []) @ext = Rake::ExtensionTask.new('extension_one') @ext_bin = ext_bin('extension_one') @platform = RUBY_PLATFORM @ruby_ver = RUBY_VERSION end context 'compile' do it 'should define as task' do Rake::Task.task_defined?('compile').should == true end it "should depend on 'compile:{platform}'" do Rake::Task['compile'].prerequisites.should include("compile:#{@platform}") end end context 'compile:extension_one' do it 'should define as task' do Rake::Task.task_defined?('compile:extension_one').should == true end it "should depend on 'compile:extension_one:{platform}'" do Rake::Task['compile:extension_one'].prerequisites.should include("compile:extension_one:#{@platform}") end end context 'lib/extension_one.{so,bundle}' do it 'should define as task' do Rake::Task.task_defined?("lib/#{@ext_bin}").should == true end it "should depend on 'copy:extension_one:{platform}:{ruby_ver}'" do Rake::Task["lib/#{@ext_bin}"].prerequisites.should include("copy:extension_one:#{@platform}:#{@ruby_ver}") end end context 'tmp/{platform}/extension_one/{ruby_ver}/extension_one.{so,bundle}' do it 'should define as task' do Rake::Task.task_defined?("tmp/#{@platform}/extension_one/#{@ruby_ver}/#{@ext_bin}").should == true end it "should depend on 'tmp/{platform}/extension_one/{ruby_ver}/Makefile'" do Rake::Task["tmp/#{@platform}/extension_one/#{@ruby_ver}/#{@ext_bin}"].prerequisites.should include("tmp/#{@platform}/extension_one/#{@ruby_ver}/Makefile") end it "should depend on 'ext/extension_one/source.c'" do Rake::Task["tmp/#{@platform}/extension_one/#{@ruby_ver}/#{@ext_bin}"].prerequisites.should include("ext/extension_one/source.c") end it "should not depend on 'ext/extension_one/source.h'" do Rake::Task["tmp/#{@platform}/extension_one/#{@ruby_ver}/#{@ext_bin}"].prerequisites.should_not include("ext/extension_one/source.h") end end context 'tmp/{platform}/extension_one/{ruby_ver}/Makefile' do it 'should define as task' do Rake::Task.task_defined?("tmp/#{@platform}/extension_one/#{@ruby_ver}/Makefile").should == true end it "should depend on 'tmp/{platform}/extension_one/{ruby_ver}'" do Rake::Task["tmp/#{@platform}/extension_one/#{@ruby_ver}/Makefile"].prerequisites.should include("tmp/#{@platform}/extension_one/#{@ruby_ver}") end it "should depend on 'ext/extension_one/extconf.rb'" do Rake::Task["tmp/#{@platform}/extension_one/#{@ruby_ver}/Makefile"].prerequisites.should include("ext/extension_one/extconf.rb") end end context 'clean' do it "should include 'tmp/{platform}/extension_one/{ruby_ver}' in the pattern" do CLEAN.should include("tmp/#{@platform}/extension_one/#{@ruby_ver}") end end context 'clobber' do it "should include 'lib/extension_one.{so,bundle}'" do CLOBBER.should include("lib/#{@ext_bin}") end it "should include 'tmp'" do CLOBBER.should include('tmp') end end it "should warn when pre-compiled files exist in extension directory" do allow(Rake::FileList).to receive(:[]). and_return(["ext/extension_one/source.c"], ["ext/extension_one/source.o"]) _, err = capture_output do Rake::ExtensionTask.new('extension_one') end err.should match(/rake-compiler found compiled files in 'ext\/extension_one' directory. Please remove them./) end end context '(extension in custom location)' do before :each do allow(Rake::FileList).to receive(:[]).and_return(["ext/extension_one/source.c"], []) @ext = Rake::ExtensionTask.new('extension_one') do |ext| ext.ext_dir = 'custom/ext/foo' end @ext_bin = ext_bin('extension_one') @platform = RUBY_PLATFORM @ruby_ver = RUBY_VERSION end context 'tmp/{platform}/extension_one/{ruby_ver}/Makefile' do it "should depend on 'custom/ext/foo/extconf.rb'" do Rake::Task["tmp/#{@platform}/extension_one/#{@ruby_ver}/Makefile"].prerequisites.should include("custom/ext/foo/extconf.rb") end end end context '(native tasks)' do before :each do allow(Rake::FileList).to receive(:[]).and_return(["ext/extension_one/source.c"], []) @spec = mock_gem_spec @ext_bin = ext_bin('extension_one') @platform = RUBY_PLATFORM @ruby_ver = RUBY_VERSION end context 'native' do before :each do allow(@spec).to receive(:platform=).and_return('ruby') end it 'should define a task for building the supplied gem' do Rake::ExtensionTask.new('extension_one', @spec) Rake::Task.task_defined?('native:my_gem').should == true end it 'should define as task for pure ruby gems' do Rake::Task.task_defined?('native').should == false Rake::ExtensionTask.new('extension_one', @spec) Rake::Task.task_defined?('native').should == true end it 'should not define a task for already native gems' do allow(@spec).to receive(:platform).and_return('current') Rake::ExtensionTask.new('extension_one', @spec) Rake::Task.task_defined?('native').should == false end it 'should depend on platform specific native tasks' do Rake::ExtensionTask.new('extension_one', @spec) Rake::Task["native"].prerequisites.should include("native:#{@platform}") end context 'native:my_gem:{platform}' do it 'should depend on binary extension' do Rake::ExtensionTask.new('extension_one', @spec) Rake::Task["native:my_gem:#{@platform}"].prerequisites.should include("tmp/#{@platform}/stage/lib/#{@ext_bin}") end end end end context '(one extension whose name with directory prefixes)' do before :each do allow(Rake::FileList).to receive(:[]).and_return(["ext/prefix1/prefix2/extension_one/source.c"], []) @ext = Rake::ExtensionTask.new('prefix1/prefix2/extension_one') @ext_bin = ext_bin('extension_one') @platform = RUBY_PLATFORM @ruby_ver = RUBY_VERSION end context 'compile' do it 'should define as task' do Rake::Task.task_defined?('compile').should == true end it "should depend on 'compile:{platform}'" do Rake::Task['compile'].prerequisites.should include("compile:#{@platform}") end end context 'compile:prefix1/prefix2/extension_one' do it 'should define as task' do Rake::Task.task_defined?('compile:prefix1/prefix2/extension_one').should == true end it "should depend on 'compile:prefix1/prefix2/extension_one:{platform}'" do Rake::Task['compile:prefix1/prefix2/extension_one'].prerequisites.should include("compile:prefix1/prefix2/extension_one:#{@platform}") end end context 'lib/prefix1/prefix2/extension_one.{so,bundle}' do it 'should define as task' do Rake::Task.task_defined?("lib/prefix1/prefix2/#{@ext_bin}").should == true end it "should depend on 'copy:prefix1/prefix2/extension_one:{platform}:{ruby_ver}'" do Rake::Task["lib/prefix1/prefix2/#{@ext_bin}"].prerequisites.should include("copy:prefix1/prefix2/extension_one:#{@platform}:#{@ruby_ver}") end end context 'tmp/{platform}/prefix1/prefix2/extension_one/{ruby_ver}/prefix1/prefix2/extension_one.{so,bundle}' do it 'should define as task' do Rake::Task.task_defined?("tmp/#{@platform}/prefix1/prefix2/extension_one/#{@ruby_ver}/prefix1/prefix2/#{@ext_bin}").should == true end it "should depend on 'tmp/{platform}/prefix1/prefix2/extension_one/{ruby_ver}/Makefile'" do Rake::Task["tmp/#{@platform}/prefix1/prefix2/extension_one/#{@ruby_ver}/prefix1/prefix2/#{@ext_bin}"].prerequisites.should include("tmp/#{@platform}/prefix1/prefix2/extension_one/#{@ruby_ver}/Makefile") end it "should depend on 'ext/extension_one/source.c'" do Rake::Task["tmp/#{@platform}/prefix1/prefix2/extension_one/#{@ruby_ver}/prefix1/prefix2/#{@ext_bin}"].prerequisites.should include("ext/prefix1/prefix2/extension_one/source.c") end it "should not depend on 'ext/extension_one/source.h'" do Rake::Task["tmp/#{@platform}/prefix1/prefix2/extension_one/#{@ruby_ver}/prefix1/prefix2/#{@ext_bin}"].prerequisites.should_not include("ext/prefix1/prefix2/extension_one/source.h") end end end context '(cross platform tasks)' do before :each do allow(File).to receive(:exist?).and_return(true) allow(YAML).to receive(:load_file).and_return(mock_config_yml) allow(Rake::FileList).to receive(:[]).and_return(["ext/extension_one/source.c"], []) @spec = mock_gem_spec @config_file = File.expand_path("~/.rake-compiler/config.yml") @ruby_ver = RUBY_VERSION @platform = 'i386-mingw32' @config_path = mock_config_yml["rbconfig-#{@platform}-#{@ruby_ver}"] allow(File).to receive(:open).and_yield(mock_fake_rb) end context 'if no rake-compiler configuration exists' do before :each do expect(File).to receive(:exist?).with(@config_file).and_return(false) _, @err = capture_output do Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compile = true end end end it 'should not generate a warning' do @err.should eq("") end it 'should create a dummy nested cross-compile target that raises an error' do Rake::Task.should have_defined("cross") Rake::Task["cross"].invoke lambda { Rake::Task["compile"].invoke }.should raise_error(RuntimeError, /rake-compiler must be configured first to enable cross-compilation/) end end it 'should parse the config file using YAML' do expect(YAML).to receive(:load_file).with(@config_file).and_return(mock_config_yml) Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compile = true end end it 'should warn if no section of config file defines running version of ruby' do allow_any_instance_of(Rake::CompilerConfig).to( receive(:find).with(@ruby_ver, @platform).and_return(nil) ) out, err = capture_output do Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compile = true end end err.should match(/no configuration section for specified version of Ruby/) end it 'should capture an action block to be executed when cross compiling' do lambda { Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compiling do |gem_spec| gem_spec.post_install_message = "Cross compiled gem" end end }.should_not raise_error end it 'should generate additional rake tasks if files are added when cross compiling' do allow_any_instance_of(Rake::CompilerConfig).to( receive(:find).and_return("/rubies/1.9.1/rbconfig.rb") ) # Use a real spec instead of a mock because define_native_tasks dups and # calls methods on Gem::Specification, which is more than mock can do. spec = Gem::Specification.new do |s| s.name = 'my_gem' s.platform = Gem::Platform::RUBY end # Gem::PackageTask calls Rake::PackageTask which sets Gem.configuration.verbose, # which initializes Gem::ConfigFile, # which gets mad if it cannot find `sysconfdir`/gemrc allow(Gem).to receive_message_chain(:configuration, :verbose=).and_return(true) ENV['RUBY_CC_VERSION'] = '1.9.1' Rake::ExtensionTask.new('extension_one', spec) do |ext| ext.cross_compile = true ext.cross_platform = 'universal-unknown' ext.cross_compiling do |gem_spec| gem_spec.files << 'somedir/somefile' end end Rake::Task['native:my_gem:universal-unknown'].execute Rake::Task.should have_defined("tmp/universal-unknown/stage/somedir") Rake::Task.should have_defined("tmp/universal-unknown/stage/somedir/somefile") end it 'should allow usage of RUBY_CC_VERSION to indicate a different version of ruby' do allow_any_instance_of(Rake::CompilerConfig).to( receive(:find) .with("1.9.1", "i386-mingw32") .and_return("/rubies/1.9.1/rbconfig.rb") ) ENV['RUBY_CC_VERSION'] = '1.9.1' Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compile = true end end it 'should allow multiple versions be supplied to RUBY_CC_VERSION' do allow_any_instance_of(Rake::CompilerConfig).to( receive(:find) .with("1.8.6", "i386-mingw32") .and_return("/rubies/1.8.6/rbconfig.rb") ) allow_any_instance_of(Rake::CompilerConfig).to( receive(:find) .with("1.9.1", "i386-mingw32") .and_return("/rubies/1.9.1/rbconfig.rb") ) ENV['RUBY_CC_VERSION'] = '1.8.6:1.9.1' Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compile = true end end it "should set required_ruby_version from RUBY_CC_VERSION, set platform, clear extensions but keep metadata" do platforms = ["x86-mingw32", "x64-mingw32"] ruby_cc_versions = ["1.8.6", "2.1.10", "2.2.6", "2.3.3", "2.10.1", "2.11.0"] ENV["RUBY_CC_VERSION"] = ruby_cc_versions.join(":") ruby_cc_versions.each do |ruby_cc_version| platforms.each do |platform| unless platform == "x64-mingw32" && ruby_cc_version == "2.11.0" rbconf = "/rubies/#{ruby_cc_version}/rbconfig.rb" end allow_any_instance_of(Rake::CompilerConfig).to( receive(:find) .with(ruby_cc_version, platform) .and_return(rbconf) ) end end allow(Gem).to receive_message_chain(:configuration, :verbose=).and_return(true) spec = Gem::Specification.new do |s| s.name = 'my_gem' s.platform = Gem::Platform::RUBY s.extensions = ['ext/somegem/extconf.rb'] s.metadata['allowed_push_host'] = 'http://test' end cross_specs = [] Rake::ExtensionTask.new("extension_one", spec) do |ext| ext.cross_platform = platforms ext.cross_compile = true ext.cross_compiling do |cross_spec| cross_specs << cross_spec end end platforms.each do |platform| Rake::Task["native:my_gem:#{platform}"].execute end expected_required_ruby_versions = [ Gem::Requirement.new([">= 1.8", "< 2.12.dev"]), Gem::Requirement.new([">= 1.8", "< 2.11.dev"]), ] cross_specs.collect(&:required_ruby_version).should == expected_required_ruby_versions cross_specs.collect(&:extensions).should == [[], []] cross_specs.collect(&:platform).collect(&:to_s).should == platforms cross_specs.collect{|s| s.metadata['allowed_push_host']}.should == ['http://test', 'http://test'] # original gemspec should keep unchanged spec.required_ruby_version.should == Gem::Requirement.new([">= 0"]) spec.platform.should == Gem::Platform::RUBY spec.extensions.should == ['ext/somegem/extconf.rb'] spec.metadata['allowed_push_host'].should == 'http://test' end after :each do ENV.delete('RUBY_CC_VERSION') end context "(cross compile for multiple versions)" do before :each do allow_any_instance_of(Rake::CompilerConfig).to( receive(:find) .with("1.8.6", "universal-unknown") .and_return("/rubies/1.8.6/rbconfig.rb") ) allow_any_instance_of(Rake::CompilerConfig).to( receive(:find) .with("1.9.1", "universal-unknown") .and_return("/rubies/1.9.1/rbconfig.rb") ) ENV['RUBY_CC_VERSION'] = '1.8.6:1.9.1' @ext = Rake::ExtensionTask.new('extension_one') do |ext| ext.cross_compile = true ext.cross_platform = 'universal-unknown' end end it 'should create specific copy of binaries for each version' do Rake::Task.should have_defined("copy:extension_one:universal-unknown:1.8.6") Rake::Task.should have_defined("copy:extension_one:universal-unknown:1.9.1") end end context "(cross for 'universal-unknown' platform)" do before :each do @ext = Rake::ExtensionTask.new('extension_one', @spec) do |ext| ext.cross_compile = true ext.cross_platform = 'universal-unknown' end end context 'fake' do it 'should chain fake task to Makefile generation' do Rake::Task["tmp/universal-unknown/extension_one/#{@ruby_ver}/Makefile"].prerequisites.should include("tmp/universal-unknown/extension_one/#{@ruby_ver}/fake.rb") end it 'should chain rbconfig tasks to fake.rb generation' do Rake::Task["tmp/universal-unknown/extension_one/#{@ruby_ver}/fake.rb"].prerequisites.should include(@config_path) end end context 'mkmf' do it 'should chain mkmf tasks to Makefile generation' do Rake::Task["tmp/universal-unknown/extension_one/#{@ruby_ver}/Makefile"].prerequisites.should include("tmp/universal-unknown/extension_one/#{@ruby_ver}/mkmf.rb") end it 'should take mkmf from rake-compiler configuration' do mkmf_path = File.expand_path(File.join(File.dirname(@config_path), '..', 'mkmf.rb')) Rake::Task["tmp/universal-unknown/extension_one/#{@ruby_ver}/mkmf.rb"].prerequisites.should include(mkmf_path) end end context 'compile:universal-unknown' do it "should be defined" do Rake::Task.task_defined?('compile:universal-unknown').should == true end it "should depend on 'compile:extension_one:universal-unknown'" do Rake::Task['compile:universal-unknown'].prerequisites.should include('compile:extension_one:universal-unknown') end end context 'native:universal-unknown' do it "should be defined" do Rake::Task.task_defined?('native:universal-unknown').should == true end it "should depend on 'native:my_gem:universal-unknown'" do Rake::Task['native:universal-unknown'].prerequisites.should include('native:my_gem:universal-unknown') end end end context '(cross for multiple platforms)' do before :each do @ext = Rake::ExtensionTask.new('extension_one', @spec) do |ext| ext.cross_compile = true ext.cross_platform = ['universal-known', 'universal-unknown'] ext.cross_config_options << '--with-something' ext.cross_config_options << {'universal-known' => '--with-known'} end end it 'should define task for each supplied platform' do Rake::Task.should have_defined('compile:universal-known') Rake::Task.should have_defined('compile:universal-unknown') end it 'should filter options for each supplied platform' do @ext.cross_config_options('universal-unknown').should eq(%w[--with-something]) @ext.cross_config_options('universal-known').should eq(%w[--with-something --with-known]) end end end end private def ext_bin(extension_name) "#{extension_name}.#{RbConfig::CONFIG['DLEXT']}" end def mock_gem_spec(stubs = {}) double(Gem::Specification, { :name => 'my_gem', :platform => 'ruby', :files => [] }.merge(stubs) ) end def mock_config_yml return @mock_config_yml if @mock_config_yml versions = { "1.8.6" => "1.8", "1.8.7" => "1.8", "1.9.3" => "1.9.1", "2.0.0" => "2.0.0", "2.1.2" => "2.1.0", RUBY_VERSION => RbConfig::CONFIG["ruby_version"] } platforms = [ "i386-mingw32", "universal-known", "universal-unknown", "x64-mingw32", RUBY_PLATFORM ] @mock_config_yml = {} platforms.collect do |platform| versions.each do |version, api_version| @mock_config_yml["rbconfig-#{platform}-#{version}"] = "/rubies/#{api_version}/rbconfig.rb" end end @mock_config_yml end def mock_fake_rb double(File, :write => 45) end end