如果gem install不支持原生扩展,则回退到纯Ruby

Nei*_*ter 11 ruby gem ruby-c-extension

我正在开发一个gem,它目前是纯Ruby,但我也一直在为其中一个功能开发一个更快的C变种.该功能在纯Ruby中可用,但有时很慢.缓慢只会影响一些潜在用户(取决于他们需要哪些功能,以及他们如何使用它们),因此如果无法在目标系统上编译,那么将gem提供给具有Ruby功能的优雅回退是有意义的.

我想在一个gem中维护该功能的Ruby和C变体,并在安装时提供gem的最佳(即最快)体验.这将使我能够从我的一个项目中支持最广泛的潜在用户.它还允许其他人的依赖宝石和项目使用目标系统上最好的可用依赖性,而不是兼容性最低的通用分母版本.

我希望require在运行时回退出现在主lib/foo.rb文件中,就像这样:

begin
  require 'foo/foo_extended'
rescue LoadError
  require 'foo/ext_bits_as_pure_ruby'
end
Run Code Online (Sandbox Code Playgroud)

但是,我不知道如何让gem安装检查(或尝试和失败)本机扩展支持,以便gem安装正确,无论它是否可以构建'foo_extended'.当我研究如何做到这一点时,我主要发现了几年前的讨论,例如http://permalink.gmane.org/gmane.comp.lang.ruby.gems.devel/1479http://rubyforge.org/ pipermail/ruby​​gems-developers/2007-November/003220.html暗示Ruby宝石并不真正支持这一功能.最近没有什么,所以我希望SO上的某些人有更多最新的知识?

我的理想解决方案是在尝试构建扩展之前检测目标Ruby不支持(或者可能根本不希望在项目级别)C本机扩展的方法.但是,如果不是太脏,尝试/捕获机制也可以.

这有可能,如果是这样的话怎么样?或者建议发布两个宝石变体(例如foofoo_ruby),我在搜索时发现,仍然是目前的最佳实践?

Nei*_*ter 4

这是迄今为止我尝试回答自己的问题的最佳结果。它似乎适用于 JRuby(在 Travis 和 RVM 下的本地安装中进行了测试),这是我的主要目标。但是,我对它在其他环境中工作的确认非常感兴趣,以及有关如何使其更通用和/或更强大的任何输入:


gem 安装代码需要Makefile的输出extconf.rb,但对它应该包含的内容没有意见。因此extconf.rb可以决定创建一个什么也不做 Makefile,而不是调用create_makefilefrom mkmf。在实践中,可能看起来像这样:

分机/foo/extconf.rb

can_compile_extensions = false
want_extensions = true

begin
  require 'mkmf'
  can_compile_extensions = true
rescue Exception
  # This will appear only in verbose mode.
  $stderr.puts "Could not require 'mkmf'. Not fatal, the extensions are optional."
end


if can_compile_extensions && want_extensions
  create_makefile( 'foo/foo' )

else
  # Create a dummy Makefile, to satisfy Gem::Installer#install
  mfile = open("Makefile", "wb")
  mfile.puts '.PHONY: install'
  mfile.puts 'install:'
  mfile.puts "\t" + '@echo "Extensions not installed, falling back to pure Ruby version."'
  mfile.close

end
Run Code Online (Sandbox Code Playgroud)

正如问题中所建议的,这个答案还需要以下逻辑来加载主库中的 Ruby 后备代码:

lib/foo.rb (摘录)

begin
  # Extension target, might not exist on some installations
  require 'foo/foo'
rescue LoadError
  # Pure Ruby fallback, should cover all methods that are otherwise in extension
  require 'foo/foo_pure_ruby'
end
Run Code Online (Sandbox Code Playgroud)

遵循此路线还需要对 rake 任务进行一些调整,以便默认的 rake 任务不会尝试在我们正在测试的不具备编译扩展功能的 Rubies 上进行编译:

Rakefile(摘录)

def can_compile_extensions
  return false if RUBY_DESCRIPTION =~ /jruby/
  return true
end 

if can_compile_extensions
  task :default => [:compile, :test]
else
  task :default => [:test]
end
Run Code Online (Sandbox Code Playgroud)

请注意,该Rakefile部分不必完全通用,它只需覆盖我们想要在本地构建和测试 gem 的已知环境(例如所有 Travis 目标)。

我注意到一件令人烦恼的事情。默认情况下,您将看到 Ruby Gems 的消息Building native extensions. This could take a while...,并且没有任何指示表明扩展编译已被跳过。但是,如果您调用安装程序,gem install foo --verbose您确实会看到添加到 的消息extconf.rb,所以这还不错。