如果gem安装时不支持,原生扩展会回落到纯Ruby上[英] Native extensions fallback to pure Ruby if not supported on gem install

本文是小编为大家收集整理的关于如果gem安装时不支持,原生扩展会回落到纯Ruby上的处理方法,想解了如果gem安装时不支持,原生扩展会回落到纯Ruby上的问题怎么解决?如果gem安装时不支持,原生扩展会回落到纯Ruby上问题的解决办法?那么可以参考本文帮助大家快速定位并解决问题。

问题描述

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

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

我希望 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 gem 并不真正支持此功能.不过最近没有什么,所以我希望 SO 上的某个人有一些最新的知识?

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

这可能吗,如果可以,怎么办?或者我在搜索时发现的发布两个 gem 变体(例如 foo 和 foo_ruby)的建议是否仍然是当前的最佳实践?

推荐答案

这是我迄今为止尝试回答我自己的问题的最佳结果.它似乎适用于 JRuby(在 Travis 和我在 RVM 下的本地安装中测试),这是我的主要目标.但是,我很想确认它在其他环境中的工作情况,以及关于如何使其更通用和/或更健壮的任何意见:

<小时>

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

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

正如问题中所建议的,这个答案还需要以下逻辑来加载主库中的 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

遵循这条路线还需要一些 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

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

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

本文地址:https://www.itbaoku.cn/post/786458.html