如果gem install不支持,本机扩展将回退到纯Ruby [英] Native extensions fallback to pure Ruby if not supported on gem install

查看:135
本文介绍了如果gem install不支持,本机扩展将回退到纯Ruby的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我希望将Ruby和C变体的特性保存在一个gem中,并从安装时提供最佳(即最快)的体验。这将使我能够从一个单一的项目中为最广泛的潜在用户提供支持。它还允许其他人的依赖项目和项目在目标系统上使用最佳可用依赖项,而不是最低公共分母版兼容性。

I会期望 require 在运行时回退以显示在主 lib / foo.rb 文件中,就像这样:

  begin 
require'foo / foo_extended'
rescue LoadError
require'foo / ext_bits_as_pure_ruby'
end

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



我的理想解决方案是在尝试构建扩展,目标Ruby不支持(或者可能不想在项目级别)C本机扩展。但是,如果不太脏,try / catch机制也可以。



这可能吗?或者建议发布两个gem变体(例如 foo foo_ruby ),我在查找时发现,仍然是当前的最佳做法?

解决方案

这是我最好的结果,试图回答我自己的问题。它似乎适用于JRuby(在Travis中测试,并在RVM下的本地安装中),这是我的主要目标。不过,我会对其在其他环境中工作的确认感兴趣,并对如何使其更加通用和/或健壮性提出任何意见:




gem安装代码需要 Makefile 作为 extconf.rb 的输出,但是没有意见应该包含什么。因此, extconf.rb 可以决定创建一个不做任何事 Makefile ,而不是调用 create_makefile 来自 mkmf 。在实践中,可能看起来像这样:
$ b

ext / foo / extconf.rb

  can_compile_extensions = false 
want_extensions = true

begin
require'mkmf'
can_compile_extensions = true
rescue例外
#这只会以详细模式显示。
$ stderr.puts不能要求'mkmf'。不是致命的,扩展是可选的。
结束


if can_compile_extensions&&需要插件
create_makefile('foo / foo')

else
#创建一个虚拟Makefile,以满足Gem :: Installer#install
mfile = open(Makefile ,wb)
mfile.puts'.PHONY:install'
mfile.puts'install:'
mfile.puts\t+'@echoExtensions not installed ,返回到纯Ruby版本。'
mfile.close

end

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

lib / foo .rb(摘录)

  begin 
#扩展目标,某些安装可能不存在
require'foo / foo'
rescue LoadError
#纯Ruby后备,应该覆盖所有扩展名为
的方法require'foo / foo_pure_ruby'
end

遵循这条路线还需要一些杂乱的耙子任务,这样默认rake任务不会尝试在我们测试的Rubies上进行编译,因为它们没有编译扩展的功能:

Rakefile(摘录)

  def can_compile_extensions 
返回false如果RUBY_DESCRIPTION =〜/ jruby /
返回true
结束

if can_compile_extensions
task:default => [:compile,:test]
else
task:default => [:test]
end

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

我注意到一个烦恼。这是默认情况下,您会看到Ruby Gems的消息构建本机扩展。这可能需要一段时间... ,并且没有指示扩展编译被跳过。但是,如果使用 gem install foo --verbose 调用安装程序,则会看到添加到 extconf.rb ,所以它也不错。


I am developing a gem, which is currently pure Ruby, but I have also been developing a faster C variant for one of the features. The feature is usable, but sometimes slow, in pure Ruby. The slowness would only impact some of the potential users (depends which features they need, and how they use them), so it makes sense to have the gem available with graceful fallback to Ruby-only functions if it cannot compile on a target system.

I would like to maintain the Ruby and C variants of the feature in a single gem, and provide the best (i.e. fastest) experience from the gem on installation. That would allow me to support the widest set of potential users from a single project of mine. It would also allow other people's dependent gems and projects to use the best available dependency on a target system, as opposed to a lowest-common-denominator version for compatibility.

I would expect the require to fallback at runtime to appear in the main lib/foo.rb file simply like this:

begin
  require 'foo/foo_extended'
rescue LoadError
  require 'foo/ext_bits_as_pure_ruby'
end

However, I don't know how to get the gem installation to check (or try and fail) for native extension support so that the gem installs correctly whether or not it can build 'foo_extended'. When I researched how to do this, I mainly found discussions from a few years back e.g. http://permalink.gmane.org/gmane.comp.lang.ruby.gems.devel/1479 and http://rubyforge.org/pipermail/rubygems-developers/2007-November/003220.html that imply Ruby gems do not really support this feature. Nothing recent though, so I am hoping someone on SO has some more up-to-date knowledge?

My ideal solution would be a way to detect, prior to attempting a build of the extension, that the target Ruby did not support (or perhaps simply not want, at the project level) C native extensions. But also, a try/catch mechanism would be OK if not too dirty.

Is this possible, if so how? Or is the advice to have two gem variants published (e.g. foo and foo_ruby), that I am finding when I search, still current best practice?

解决方案

This is my best result attempting to answer my own question to date. It appears to work for JRuby (tested in Travis and on my local installation under RVM), which was my main goal. However, I would be very interested in confirmations of it working in other environments, and for any input on how to make it more generic and/or robust:


The gem installation code expects a Makefile as output from extconf.rb, but has no opinion on what that should contain. Therefore extconf.rb can decide to create a do nothing Makefile, instead of calling create_makefile from mkmf. In practice that might look like this:

ext/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

As suggested in the question, this answer also requires the following logic to load the Ruby fallback code in the main library:

lib/foo.rb (excerpt)

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

Following this route also requires some juggling of rake tasks, so that the default rake task doesn't attempt to compile on Rubies that we're testing on that don't have capability to compile extensions:

Rakefile (excerpts)

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

Note the Rakefile part doesn't have to be completely generic, it just has to cover known environments we want to locally build and test the gem on (e.g. all the Travis targets).

I have noticed one annoyance. That is by default you will see Ruby Gems' message Building native extensions. This could take a while..., and no indication that the extension compilation was skipped. However, if you invoke the installer with gem install foo --verbose you do see the messages added to extconf.rb, so it's not too bad.

这篇关于如果gem install不支持,本机扩展将回退到纯Ruby的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆