Ruby C 库的扩展包装器无法加载已安装的库

Ruby extension wrapper around C library fails to load installed library

Ruby 扩展 davenport-ruby 到 C 库 davenport 将无法在 Ubuntu 和 Debian 上正确加载。它在开发机器 (MacOS) 上工作正常,如冒烟测试 Ruby 项目的自述文件所示,dvt

RubyGems 加载器(通过捆绑器)按如下方式编译和安装扩展:

~/dvt$ rm -rf ~/.bundle
~/dvt$ bundle install
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.0.2
Fetching davenport 1.0.2.pre
Installing davenport 1.0.2.pre with native extensions
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
~/dvt$ bundle info davenport
  * davenport (1.0.2.pre)
    Summary: Ruby binding for the Davenport library
    Homepage: https://github.com/wbreeze/davenport-ruby
    Path: /home/deploy/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0\
      /gems/davenport-1.0.2.pre
~/dvt$ ls /home/deploy/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0\
      /gems/davenport-1.0.2.pre/lib/davenport_ruby/
davenport_ruby.so
~/dvt$

但是尝试 运行 程序会产生:

deploy@localhost:~/dvt$ ruby test.rb
Traceback (most recent call last):
    6: from test.rb:1:in `<main>'
    5: from /home/deploy/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems\
       /core_ext/kernel_require.rb:34:in `require'
...
    1: from /home/deploy/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems\
       /core_ext/kernel_require.rb:54:in `require':\
  libdavenport.so.0: cannot open shared object file: No such file or\
    directory - /home/deploy/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0\
      /gems/davenport-1.0.2.pre/lib/davenport_ruby/davenport_ruby.so\
(LoadError)

库文件 libdavenport.so.0 存在于 /usr/local/lib 中。使用 ruby -I /usr/local/lib test.rb 制作加载路径的那一部分会产生相同的结果。

库文件 davenport_ruby.so 存在于 /home/deploy/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/davenport-1.0.2.pre/lib/davenport_ruby 中,如下所示:

/home/deploy/.rbenv/versions/2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require': libdavenport.so.0: cannot open shared\
object file: No such file or directory - /home/deploy/.rbenv/versions/2.6.3\
/lib/ruby/gems/2.6.0/gems/davenport-1.0.2.pre/lib/davenport_ruby\
/davenport_ruby.so (LoadError)
~/dvt$ ls -l ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/\
davenport-1.0.2.pre/lib/davenport_ruby/
total 72
-rwxr-xr-x 1 deploy deploy 69640 Jul 25 17:02 davenport_ruby.so
~/dvt$

Ruby davenport 扩展的已发布 1.0.2.pre 版本来自 this PR,其中 link 显示了所有代码位(以及相当多的实验)。是扩展部分的某些细节还是部分 gem 不太正确?

davenport_ruby.so

.so文件是安装gem时编译的共享库文件。它不存在于 gem:

的内容中
~/davenport-ruby/dltem[i1]$ gem unpack davenport-1.0.2.pre.gem
Unpacked gem: '/Users/dcl/davenport-ruby/dltem/davenport-1.0.2.pre'
~/davenport-ruby/dltem[i1]$ ls -R
davenport-1.0.2.pre davenport-1.0.2.pre.gem

./davenport-1.0.2.pre:
History.txt README.rdoc Rakefile    ext     lib

./davenport-1.0.2.pre/ext:
davenport_ruby

./davenport-1.0.2.pre/ext/davenport_ruby:
davenport_ruby.c    extconf.rb

./davenport-1.0.2.pre/lib:
davenport.rb
~/davenport-ruby/dltem[i1]$

当 RubyGems 安装 gem 时,它会检测 gem 规范中的 s.extensions << 'ext/davenport_ruby/extconf.rb' 并使用 ruby 执行该文件吗?生成的 make 文件是否 运行 make

什么可以使 ruby test.rb 变为 运行 而不会出现错误?

This gist 包含 libdavenport.so.0 和 davenport_ruby.so 中唯一全局符号的比较,以及 gem environmentruby -e 'puts $:.join("\n")' 命令的输出。

ldd

about the not found (LoadError) problem suggests using ldd to verify the linking. (There is a related question 没有答案,但在评论中有类似的建议。)

事实上,扩展库虽然是内置的,但不会 link 到已安装的 libdavenport.so.0:

~/dvt$ bundle install
Fetching gem metadata from https://rubygems.org/.
Using bundler 2.0.2
Fetching davenport 1.0.2.pre
Installing davenport 1.0.2.pre with native extensions
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
~/dvt$ ldd ~/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/davenport-1.0.2.pre/lib/davenport_ruby/davenport_ruby.so
    linux-vdso.so.1 (0x00007fff2d3e3000)
    libdavenport.so.0 => not found
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f9a5be42000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9a5baa3000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f9a5c349000)
~/dvt$

ldconfig

This very old question 在 AskUbuntu 上建议使用 ldconfig 命令。由于 libdavenport.so.0 安装在 /usr/local/lib 中,命令是 ldconfig /usr/local/lib。然而 运行 它需要一个不同的登录名来获得 root 权限。切换回正在安装 Ruby 程序的 deploy 帐户:

~/dvt$ ruby test.rb
Hola
[1, 3, 2, 4]
~/dvt$

瞧。它现在正在工作。问题变成了如何让它与 deploy/install 以及没有 root 权限的用户一起工作。 (安装库 libdavenport 也需要 root 权限,尽管它是从源代码编译和安装的。)

libdavenport.so.0: cannot open shared object file: No such file or\
directory - /home/deploy/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0\
  /gems/davenport-1.0.2.pre/lib/davenport_ruby/davenport_ruby.so

此错误意味着本机扩展 davenport_ruby.so 无法加载 Davenport 的共享库 libdavenport.so.0。您需要使用 ldconfig 或通过将其路径添加到 LD_LIBRARY_PATH.

来确保共享库位于您的加载路径中

旧的错误答案:

Ruby C 扩展必须针对您 运行 安装它们的平台进行编译。如果您只是下载一些使用扩展的 Ruby 代码,它不会开箱即用。

您是使用 gem 还是 bundle 构建扩展的?您是否 运行 在 Ubuntu 机器上尝试 运行 脚本?

gem 不应包含 .so。当您安装 gem 时,它应该构建 .so 并将其放在 Ruby 的加载路径中的某个位置,以便脚本可以需要它。

假设您实际安装了 gem 并成功构建了 .so,我可以想到两个可能的问题

  1. 您使用错误的工具或选项安装了 gem,因此将 .so 放在了错误的位置
  2. 你 运行宁 Ruby 的方式不对(你使用的是 rbenv 还是 rvm?)所以它在错误的地方寻找 .so

运行 gem environmentruby -e 'p $:' 将您的 gem 安装目录与 Ruby 的加载路径进行比较。

运行 ldconfig /usr/local/lib root 原来是答案。它使加载程序能够在 运行 时间找到已安装的 libdavenport.so.0 库。

对于后续问题,答案是 make install 可能无法消除某些系统上 运行 ldconfig 的需要。有一个针对另一个图书馆的旧问题,该图书馆仍然开放,位于 esnet/iperf。他们想不通。如果他们这样做了,他们就不会更新问题。一个较新的问题反对 libcheck/check 也有类似的挣扎。

libtool 的当前 Gnu 文档已在其 implementation issues

The install Makefile target should warn the package installer to set the proper environment variables (LD_LIBRARY_PATH or equivalent), or run ldconfig.

本例中的 "solution" 是 Davenport 库的文档解决方案, 对 README 的更新 this PR.

关于库链接和加载有一个很好的简短解释 Cprogramming.com(这是广告'支持但并不痛苦;这篇文章获得了参考)。