2013 年 10 月 24 日
关注 @alloyTL;DR:作为 CocoaPods gem 安装流程一部分的编译已死。预编译 gem 万岁!
CocoaPods gem 现在可以无需安装 Xcode 命令行工具和修复 OS X(Mountain Lion 和 Mavericks)上的 Ruby 头文件位置即可安装。
潜在问题的历史
虽然 CocoaPods 及其大多数依赖项都是用纯 Ruby 实现的,但有一个依赖项需要一些 C 代码,或者用 Ruby 术语来说,即“Ruby C 扩展”。
通常,“Ruby C 扩展”将在 gem 安装期间在用户的计算机上编译。虽然这提高了可移植性,但确实增加了编译器和 Ruby C 头文件的要求,而 OS X 默认情况下不提供这两者。
公平地说,Mavericks 预装了命令行工具,因此也预装了编译器,但它没有将 Ruby C 头文件放在正确的位置,并且需要手动干预。
我们遇到的具有 C 扩展的依赖项是Xcodeproj,这个库为我们的 Xcode 项目解析和生成提供支持。它通过利用名为CFPropertyList的 OS X Core Foundation API 来实现这一点。此 API 允许我们读取任何类型的 PList,包括 Xcode 仍在使用的已弃用的 ASCII 格式,并生成 Xcode 可读的 PList。虽然在实现 Xcodeproj 时有一些纯 Ruby 库可以读取或写入其中一种 PList 格式,但没有一个库能让我们满意地实现所有这些格式。最后,由于 Xcode 项目是一种未记录的格式,我们不想花很多时间来实现它,并且如果 Apple 决定更改格式,我们可能不得不更换它。
UX 视角
如果您对有关如何使用 Ruby 和现状来完成任务的分支讨论不感兴趣,您可以跳到“解决方案”。
这些问题让纯 Ruby 用户的开箱即用 UX 变得非常糟糕。CocoaPods 是一个工具,它的用户中有很多不是 Ruby 开发人员。也就是说,Ruby 在某种程度上是一个实现细节。这自然会导致用户感到沮丧。
更糟糕的是,这些用户——出于愿意给 CocoaPods 第二次机会——通常会通过 Google 搜索解决方案,这会将他们带入我所说的 Ruby 开发人员的“开发人员泡沫”。
在这个“开发人员泡沫”中,没有理智的 Ruby 环境,除非你至少构建自己的 Ruby,但最好包含以下一个或多个工具:homebrew、RVM、rbenv以及许多其他替代方案。
这里有一篇最近的博客文章给出了这种错觉,这里有一条典型的推文说明了这个建议如何误导纯 Ruby 用户
Soooo... 我需要为 rvm、ruby-install 和 rbenv 安装 homebrew,然后才能`gem install cocoa pods`? #shitshow
— ¡ɜɿoɾɪɹℲ (@frijole) 2013 年 10 月 21 日
真正的烂摊子。
可悲的是,这甚至不是必要的。如果你是一个 Ruby 开发人员,更不用说你是一个纯用户了。我们 Ruby 社区需要修复这种观点,并从外部审视我们自己陷入的混乱,并在上下文中更好地构建我们的建议。但现在我跑题了。
解决方案
解决方案很简单,在我们的案例中是双重的
建议使用 OS X 附带的 Ruby 版本。
唯一剩下的——但非常有效——的担忧是,默认情况下,你必须在安装 gem 时使用 sudo
。(不过,这只是在 gem 安装期间才是一个问题。)
如果你不想为此过程授予 RubyGems 管理员权限,你可以通过将 --user-install
标志传递给 gem install
或配置 RubyGems 环境来告诉 RubyGems 安装到你的用户目录。我们认为后者是最好的解决方案。为此,编辑你主目录中的 .profile
文件,并添加或修改它以包含以下行
export GEM_HOME=$HOME/gems
export PATH=$GEM_HOME/bin:$PATH
请注意,如果你选择使用 --user-install
选项,你仍然需要配置你的 .profile
文件来设置 PATH
或使用带完整路径作为前缀的命令。你可以使用 gem which cocoapods
来找出 gem 安装在何处。例如:
$ gem install cocoapods --user-install
$ gem which cocoapods
/Users/eloy/.gem/ruby/1.8/gems/cocoapods-0.27.0/lib/cocoapods.rb
$ /Users/eloy/.gem/ruby/1.8/gems/cocoapods-0.27.0/bin/pod install
为 OS X 附带的 Ruby 版本提供预构建的二进制文件。
在我们的示例中,这意味着在 OS X 10.8.x 上为 Ruby 1.8.7 预先构建的 C 扩展版本,在 OS X 10.9.x 上为 Ruby 2.0.0 预先构建的 C 扩展版本。在这些版本上,用户不需要安装 Xcode 命令行工具,也不需要修复 Ruby C 头文件。
但是,如果你使用的是自定义 Ruby 版本,无论它是否与我们提供预构建二进制文件的 Ruby 版本完全相同,你都需要编译 C 扩展,因此需要 Xcode 命令行工具。
这一切都是透明进行的,所以你无需过多考虑。但是,如果你出于某种原因希望控制此操作,则可以使用 XCODEPROJ_BUILD
环境变量。将其设置为 1
以始终编译,或设置为 0
以从不编译。
请注意,你可以通过 gem install
输出中的以下句子来识别你正在使用预构建的二进制文件
[!] You are using the prebuilt binary version of the xcodeproj gem.
RubyGems 开发人员注意事项
在弄清楚以何种方式最好地满足我们用户的需求的过程中,我得到了以下见解
RubyGems 内置预构建的二进制文件支持。
如果存在具有以下文件名的 gem,则 RubyGems 会自动安装一个特定于 CPU 架构 + 操作系统版本的 gem:[NAME]-[VERSION]-[ARCH]-[PLATFORM].gem
。例如:xcodeproj-0.14.0-universal.x86_64-darwin13.gem
。
虽然这允许你针对特定架构和操作系统版本进行预构建,但它不允许你控制你拥有的 Ruby 版本。这意味着它对我们来说不是一个选项,因为我们必须检查你正在使用的 Ruby 实际上是否是预安装的 OS X Ruby 版本。(如果有用于控制 Ruby 版本的修饰符,请告诉我,我将更新此部分。)
无需 make
即可安装 RubyGems。
因为我们必须检查你正在使用的 Ruby 版本是否为 OS X 预装版本,我们必须在安装 gem 时执行这些检查。通常,RubyGems 作者会“滥用”其 extconf.rb
以在不需要编译的情况下生成无操作的 Makefile
。然而,这意味着用户仍然需要安装 make
,而 OS X 10.8 中没有这种情况。
幸运的是,RubyGems 维护者之一 Eric Hodel 向我指出了一个未记录的功能。你可以这样指定扩展,而不是像这样指定
s.extensions = "ext/xcodeproj/extconf.rb"
你也可以指定一个 Rakefile
,在这种情况下,RubyGems 将停止运行 make
,或者无论你需要运行什么,都取决于你的 rake 任务
s.extensions = "ext/xcodeproj/Rakefile"
预构建二进制包助手。
rake-compiler 库在预构建扩展和理解 RubyGems 如何处理这些 gem 文件名格式方面非常有帮助。但是,如果你要针对非常具体的 Ruby 版本,无论用户/机器如何,都与我们一样,那么将 extconf.rb
的结果(即 Makefile
和可能的 extconf.h
)作为供应商,并使用它们来构建扩展会简单得多。这是 我们的最终解决方案。