RubyGems 导航菜单

博客

返回博客帖子

修剪 YAML 牦牛

你是否见过此错误?

$ gem install rails --pre
ERROR:  While executing gem ... (NameError)
  uninitialized constant Psych::Syck
$

是的,我也遇到过。今天,我们将讨论此错误的来源,以及我们为修复它所需要做的事项。

TL;DR

升级 rubygems,使此错误消失。但是,长期解决方案是修复 rubygems.org。

Gemspec 与 YAML

当你打包你的宝石时,生成的 .gem 文件包含你的 gemspec 的 YAML 表示。你的 gemspec 的 YAML 表示包含你的 gemspec 所包含的所有内容,包括作者信息和依赖信息。

生成的宝石文件只是一个 tar 文件

$ gem fetch rails
Fetching: rails-3.1.0.gem (100%)
Downloaded rails-3.1.0
$ file rails-3.1.0.gem 
rails-3.1.0.gem: POSIX tar archive

此 tar 文件包含两个压缩文件。一个是你宝石的实际内容,即 rb 文件等。另一个 gz 文件是你宝石的元数据

$ tar xvf rails-3.1.0.gem 
x data.tar.gz
x metadata.gz

此元数据是你 gemspec 的 YAML 格式。我们可以轻松地使用 gzcat 命令来检查 YAML 数据

$ gzcat metadata.gz 
--- !ruby/object:Gem::Specification 
name: rails
version: !ruby/object:Gem::Version 
  hash: 3
  prerelease: 
  segments: 
  - 3
  - 1
  - 0
  version: 3.1.0
platform: ruby
...

YAML 和 Ruby 的标准库

Ruby 已经附带一个 YAML 解析库很长一段时间了。此库(称为“syck”)是一个定制的 YAML 1.0 解析器,并且自 Ruby 1.8 起便是 Ruby 的一部分。不幸的是,该库已被废弃。

YAML 解析对于 Ruby 来说很重要,因此,不是移除 Syck,而是用一个称为 Psych 的新库来取代它。Psych 是一个 YAML 1.1 解析器,但由 libyaml 提供支持。libyaml 是由编写 YAML 规范的团队开发的,并且被认为是规范的实现。

不幸的是,此转换可能会引起隐蔽但讨厌的问题。

处理 =

我们在从 rubygems.org 下载时看到的问题是由于 Syck 和 Psych 之间的循环往复制问题。让我们比较一下 Syck 和 Psych 如何序列化 = 符号

>> RUBY_VERSION
=> "1.8.7"
>> require 'yaml'
=> true
>> YAML.dump ['=']
=> "--- \n- \"=\"\n"
>>

在上述示例中,我们使用 Syck 来转储一个等号。请注意,这个等号周围有双引号。现在,让我们用 Psych 来尝试相同的事情

irb(main):001:0> RUBY_VERSION
=> "1.9.4"
irb(main):002:0> require 'yaml'
=> true
irb(main):003:0> YAML.dump ['=']
=> "---\n- =\n"
irb(main):004:0>

请注意,在此示例中,等号周围没有双引号。这两个都是有效的 YAML 文档。让我们看看当我们将 YAML 从 Psych 馈送到 Syck 中会发生什么情况

>> RUBY_VERSION
=> "1.8.7"
>> yaml = "---\n- =\n"
=> "---\n- =\n"
>> YAML.load yaml
=> [#<YAML::Syck::DefaultKey:0x1026d0210>]
>>

这就是结果!我们可以让 Syck 返回一个奇怪的对象,即使给了它一个有效的 YAML 文档。但是,这与 gemspec 和 rubygems.org 有什么关系呢?

Gemspec 重访

请记住宝石规范在宝石打包时会转储到YAML。我们的宝石规范中包含等号(并且不少见)。考虑宝石规范中的定义的依赖项

Gem::Specification.new do |s|
  ...
  s.add_dependency('activesupport', '= 3.1.0')
  ..
end

这些依赖项被序列化到生成的 YAML 文件中。如果我们使用 Psych 打包此文件,并检查生成的 yaml,我们可以在其中找到声明依赖项的部分

$ gzcat metadata.gz
...
dependencies:
- !ruby/object:Gem::Dependency
  name: activesupport
  requirement: &70133834330380 !ruby/object:Gem::Requirement
    none: false
    requirements:
    - - =
      - !ruby/object:Gem::Version
        version: 3.1.0
...

注意元数据中未加引号的等号。

rubygems.org

上面我们看到的规范中存在的问题是,rubygems.org 仍将 syck 用作其 YAML 解析器。当服务器解析宝石 YAML 文件时,它将等号转换成 Syck DefaultKey。

如果您查看某些宝石的运行时依赖项,您可以在 rubygems.org 网站上看到这个问题的表现

runtime deps screenshot

一个简单的 google 搜索 将显示这是一个普遍的问题。

命令行错误

下载宝石信息时,rubygems.org 将以 marshall 格式发送依赖项信息。在依赖项中包含 DefaultKey 对象的宝石将被序列化该对象并发送给客户端。如果客户端不使用 Syck(当有 libyaml 时,对于 1.9.2+ 来说它是默认的),那么将找不到 Syck 常量,并且将出现“未初始化常量”错误。

我们如何才能修复此问题?

我们有两种方法来解决此问题。解决此问题的第一种方法是升级 RubyGems。RubyGems 包含代码,可在安装宝石时解决此问题。但它并没有修复此问题。

修复所有用户的错误的唯一方法是将 rubygems.org 升级为使用 Psych 作为 YAML 解析器。升级 rubygems.org 将防止未知对象进入发送给用户的 marshall 数据。

我们正在进行此项升级,但我们可以利用您的帮助!具体来说,我们需要让 delayed job 与 Psych 兼容。一旦我们克服这一障碍,我认为很容易将 rubygems.org 升级。

感谢您的聆听!

<3<3<3

tenderlove