如果gem install不支持,本机扩展将回退到纯Ruby [英] Native extensions fallback to pure Ruby if not supported on gem install
问题描述
我正在开发一个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安装代码需要 ext / foo / extconf.rb 正如问题中所建议的,这个答案还需要以下逻辑来加载主库中的Ruby后备代码: lib / foo .rb(摘录) 遵循这条路线还需要一些杂乱的耙子任务,这样默认rake任务不会尝试在我们测试的Rubies上进行编译,因为它们没有编译扩展的功能: Rakefile(摘录) 请注意 我注意到一个烦恼。这是默认情况下,您会看到Ruby Gems的消息 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 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. 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 ext/foo/extconf.rb 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) 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) Note the I have noticed one annoyance. That is by default you will see Ruby Gems' message 这篇关于如果gem install不支持,本机扩展将回退到纯Ruby的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋! Makefile
作为 extconf.rb
可以决定创建一个不做任何事 Makefile
,而不是调用 create_makefile
来自 mkmf
。在实践中,可能看起来像这样:
$ b
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
begin
#扩展目标,某些安装可能不存在
require'foo / foo'
rescue LoadError
#纯Ruby后备,应该覆盖所有扩展名为
的方法require'foo / foo_pure_ruby'
end
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目标)。
构建本机扩展。这可能需要一段时间...
,并且没有指示扩展编译被跳过。但是,如果使用 gem install foo --verbose
调用安装程序,则会看到添加到 extconf.rb
,所以它也不错。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
foo
and foo_ruby
), that I am finding when I search, still current best practice?
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: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
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
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
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).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.