Ruby Gemfile 详解

jackxu · July 27, 2015 · Last by Thomastar replied at November 07, 2017 · 28071 hits
Topic has been selected as the excellent topic by the admin.

前几天读到的一篇博客,觉得内容很详实,就翻译了下给大家分享下。绝大部分为直译,极少数地方加了点自己的注释,若有不周到地方,还望大家指出。如果有排版方面的问题,也请指出。

原文:http://tosbourn.com/what-is-the-gemfile/

作为 Ruby 开发者,我们一直在使用 Gemfile,并且大部分人知道一些关于 Gemfile 的基础知识。在这篇文章里,我想更加深入到 Gemfile 里面去看看通过 Gemfile 所能做的一切。

什么是 Gemfile

Gemfile 是我们创建的一个用于描述 gem 之间依赖的文件。gem 是一堆 Ruby 代码的集合,它能够为我们提供调用。你的 Gemfile 必须放在项目的根目录下面,这是 Bundler 的要求,对于任何的其他形式的包管理文件来说,这也是标准。这里值得注意的一点是 Gemfile 会被作为 Ruby 代码来执行。当在 Bundler 上下文环境中被执行的时能使我们访问一些方法,我们用这些方法来解释 gem 之间的 require 关系。

创建 Gemfile

首先我们要做的就是告诉 Gemfile 到那里去找到这些 gems, 这就是 gem 的源。

我们使用#source方法来做这件事情

source "https://rubygems.org"

这里并不推荐一个项目有多个源。对于 99% 的项目,你的 Gemfile 的源都会被要求设置为https://rubygems.org,对于一个源,唯一的要求是它必须是一个合法的 Rubygems 的 repo。

源的优先级

现在我们来探讨下关于 gem 源的优先级。 我们在 Gemfile 的顶部位置定义一个源的同时,我们也可以针对每个 gem 定义一个源。我们也能够为一个本地的 gem 定义一个路径或者是为 gem 定义一个 git 路径,比如说 GitHub 之类的(我们在后面点讲到这点)。 当 Bundler 尝试定位一个 gem 的时候,它会首先查看这个 gem 有没有显示的设置源,如果有,就先使用这个源。如果你在设置 gem 的时候有使用 source, path 或者 git 依赖的话,Bundler 将会先在这些地方找,然后再去其他地方寻找。如果没有被显示设置的话,Bundler 将会依照你 Gemfile 里面定义的源的顺序来找。如果一个 gem 能够在多个源里面被找到的话(虽然这是极为罕见的,因为你最好只定义一个源),你将会得到一个 warning 来提示你哪个源被使用了。

你能够使#source 作为一个 block 来调用

source "https://my_awesome_source.com" do
   gem "my_gem"
   gem my_other_gem
end

带验证的源

有些源需要你使用验证才能够被设定。Bundler 有一个设置选项使得你可以为每个源设置用户名和密码

bundle config my_gem_source.com my_username:my_password

这是任何希望通过 Bundler 来安装 gem 都必须要的因为它不会被放入版本管理里面。你也可以直接在 Gemfile 中设置你的验证信息,当然,这些验证信息也会被 commit 进你的版本管理工具。如下所示

source "https://username:password@my_gem_source.com"

你在源里面的设置,都会被你以 bundle config 的方式设置的东西所覆盖。

设置 Ruby 信息

如果你的应用程序需要使用一个特别的 Ruby 版本或是引擎,我们都能够在 Gemfile 里面进行设置。

ruby "1.9.3", :patchlevel => "247", :engine => "jruby", :engine_version => "1.6.7"

当设定这个的时候,需要的唯一点信息就是 ruby 的版本(我们这里使用 1.9.3) * :pathlevel 声明了 Ruby 的 patch level * :engine 声明了使用的 Ruby 引擎 * :engine_version 声明了引擎的版本 (如果这个被设置了,engine 也需要被设置)

设置 Gems

现在我们到了 Gemfile 的核心,设置你的 gems。最基本的语法如下:

gem "my_gem"

这里 my_gem 是 gem 的名字,gem 的名字是唯一要求的参数,此外还有几个可以选择的参数可以使用。

设置 Gem 的版本

对于一个 gem,你最常做的事情就是设置它的版本,如果你不设置版本的话,你也可以说任意的版本都可以。

gem "my_gem", ">= 0.0"

这里有 7 个操作符供你用来设置你的 gem

* = Equal To "=1.0"
* != Not Equal To "!=1.0"
* > Greater Than ">1.0"
* < Less Than "<1.0"
* >= Greater Than or Equal To ">=1.0"
* <= Less Than or Equal To "<=1.0"
* ~> Pessimistically Greater Than or Equal To "~>1.0"

Pessimistically Greater Than or Equal To

~> 操作能够让你使用这个 gem 的未来的某个安全的版本。如果你觉得使用一个大的版本更安全,你能够像下面这样声明。

gem "my_gem", "~> 2.0"

这能够允许你安装任意的 2.x 版本的 gem,但是 3.x 版本是不被允许的。或许你对这么宽泛的版本感到不爽,你也可以声明一个更具体的版本,如下

gem "my_gem", "~> 2.5.0"

这能够让你使用 2.5.0 到 2.6.0 之间的版本。下面的例子能够让你更加理解~> 操作符

* gem "my_gem", "~> 1.0" > gem "my_gem", ">= 1.0", "< 2.0"
* gem "my_gem", "~> 1.5.0" > gem "my_gem", ">= 1.5.0", "< 1.6.0"
* gem "my_gem", "~> 1.5.5" > gem "my_gem", ">= 1.5.5", "< 1.6.0"

设置 gem 被 required

如果你使用 Rails 的话,这点小技巧可能被隐藏了,但是在你的 config/application.rb 文件里面你能看到这么一行代码。

Bundler.require(:default, Rails.env)

它的意思是 require 所有没有被放入 group(后面会讲到这个概念)里面的 gems 和所有放入和当前 rails 环境(RAILS_ENV, development, test, production) 同名的 group 里面的 gems。 默认方式下,如果你在 Gemfile 里面包含一个 gem,当 Bundler.require 被调用的时候会被包含进来。我们也能通过下面的设置让 gem 不被包含进来 (译者注释:这样你就只能安装这个 gem,在使用的时候必须在你的代码里手动的添加require ‘my_gem’来调用 my_gem 里面的方法了。为什么需要这样呢,因为并不是所有的地方都需要使用这个 gem,比如你在 rake task 里面使用了 my_gem, 而其他地方没有使用,故你只需要在这个 gem require 到 task 里面,避免了所有的进程都把这个 gem 加载进去)

gem "my_gem", require: false

当然你也可以指定哪些文件夹被 required 的,如下:

gem "my_gem", require: ["my_gem/specific_module/my_class", "my_gem"]

这点在当你的 gem 有很多功能的,你必须每次手动 require 的时候非常有用。

gem 分组

正如我上面提到的一样,一个 gem 可以属于一个或多个 group,当它不属于任何 group 的时候,它被放入了:default group。 有两种方法你可以对一个 gem 分组。第一种是对 group 属性进行赋值,如下所示:

gem "my_gem", group: :development

它的意思是,这个 gem 只在 development 环境下被 require。这也意味着当你在安装 gems 的时候,你可以指定某个 group 下面的 gems 不被安装,这样在一定程度上能加快 gem 的安装。

bundle install --without development test

上面的意思是安装除 development 和 test group 意外的所有 gems。 第二种 gem 分组的方法就是你可以将 gems 放入一个 block 里面,如下所示:

group :development do
   gem "my_gem"
   gem "my_other_gem"
end

这看上去更美观,并且你也可以设置多个 group。

group :development, :test do
  gem "my_gem"
  gem "my_other_gem"
end

如果你想让某个 group 变成可选的形式,你也可以像下面这样,设置 optional: true

group :development, optional: true do
   gem "my_gem"
   gem "my_other_gem"
end

当上面被设置时,为了安装 development group 下面的 gems,需要运行bundle install —with development

设置 gem 的平台

如果某个 gem 只能在某个平台上使用,你也可以在 gemfile 里面设置。平台的原理和 group 很类似,但不同的是你不需要去通过—without 这样的 option 去指定,它会自动根据平台判断执行。

gem "my_gem", platform: :jrubygem "my_other_gem", platform: [:ruby, :mri_18]

下面是一个不同平台的 list。

* ruby – C Ruby (MRI) or Rubinius, but not Windows
* ruby_18 to ruby_22 – ruby & (version 1.8 .. version 2.2)
* mri – Same as ruby, but not Rubinius
* mri_18 to mri_22 – mri & (version 1.8 .. version 2.2)
* rbx – Same as ruby, but only Rubinius (not MRI)
* jruby – JRuby
* mswin – Windows
* mingw – Windows 32 bit mingw32 platform (aka RubyInstaller)
* mingw_18 to mingw_22 – mingw & (version 1.8 .. version 2.2)
* x64_mingw – Windows 64 bit mingw32 platform
* x64_mingw_20 to x64_mingw_22 – x64_mingw & (version 2.0 .. version 2.2)

我发现平台真的非常有用,当一个开发团队在不同平台开发的时候。当你 team 的一个开发者使用的是 Windows 平台的时候,你可能需要不同版本的 gem 来支持。我经常使用下面的 block 语法来使用 platform 设定。

platforms :jruby do
  gem "my_gem"
  gem "my_other_gem"
end

设置 gem 的源

ok,现在我们来讲设置 gem 的源,如下所示:

gem "my_gem", source: "https://my_awesome_gemsite.com"

如果这个 my_gem 在 source 里面找不到的话,Bundler 也不会去 default 的源里面找,所以找不到的情况下这个 gem 就不会被安装。

从 git 安装 gem

你可以设置 gem 的安装源为一个 git repo,比如 GitHub, 这只需要你将 source 属性替换为 git。你可以设置这个 repo 的链接为 HTTP(S), SSH, GIT 等协议,但最好使用 HTTP(S) 和 SSH,因为其他的会使你可能成为 man-in-the-middle 攻击的受害者。如果你把 gem 放入到 repo 里面,你必须要在 repo 根目录文件夹下面有一个.gemspec 文件。这里面需要包含一个合法 gem 的声明。如果你没有提供这个文件,Bundler 会尝试创建一个,但是他不会被依赖。如果你尝试去 include 一个没有提供.gemspec 文件的 git repo 里面的 gem,你必须指定一个版本号。

你可以为 gem 设置 branch,tag,ref,默认是使用 master branch。你也可以强制 Bundler 扩展 submodule,通过以下方式来设置:

gem "my_gem", git: "[email protected]/tosbourn/my_gem", branch: test_branch, submodules: true

如果你有多个 gem 来自同一个 git repo,你也可以通过下面 block 形式组织起来。

git "[email protected]:tosbourn/my_gems.git" do
  gem "my_gem"
  gem "my_other_gem"
end

设置 Git 作为 source

你可以设置一个 URL 来作为一个更广义的源,你可以通过调用#git_source 方法并将 name 作为参数传进去,以及一个接收一个参数的 block,并返回一个 string 作为 repo 的 URL。如下所示:

git_source(:custom_git){ |repo| "https://my_secret_git_repos.com/#{repo}.git" }
gem "my_gem", custom_git: "tosbourn/test_repo"

BitBucket 和 Github 的 helper method

因为 BitBucket 和 Github 都是比较流行的 git repo host,所以有两者的 helper method。在两者里面,Bundler 都默认 repo 是 public 的。

gem "my_gem", github: "tosbourn/my_gem" 
gem "my_gem", bitbucket: "tosbourn/my_gem"

你也可以设置两者的 branch。当用户名和 repo 名字一致的时候,可以省略一个。

gem "rails", github: "rails"
gem "rails", bitbucket: "rails"

注意:在 Bundler 2 出来之前,你不能使用:github 这个参数,目前它是使用 git://协议的,就是前面讲过的可能会受到 man-in-the-middle 攻击的。还有一个 helper :gist, 如果你 Github 上是以 gist 的形式存放的话就能够使用它。你可以只使用 gist ID 作为 path,也可以像:github, :bitbucket 那样传入:branch 参数。

gem "my_gem", :gist => "5935162112", branch: "my_custom_branch"

用 path 包含本地 Gem

你可以通过传入:path 参数来依赖你本地的 gems。

gem "my_gem", :path => "../my_path/my_gem"

如果你传入一个相对路径的话(如上),这个路径是相对于你 Gemfile 的路径的。如果你想把某个文件夹下所有的 gems 都包含进去的话,你可以使用如下的 block。

path "../my_path/gems" do
  gem "my_gem"
  gem "my_other_gem"
end

有一点值得注意的是,如果你使用的是 path 的话,Bundler 是不会编译 c extension 的。

选择性的安装 gems

有时候你想在某个前提条件被满足的情况下安装这个 gem,比如你系统里面是否有某个程序。下面这个方法能够接收一个 proc 或 lambda,下面的例子中我们将在你的系统是 mac 的时候安装这个 gem

install_if -> { RUBY_PLATFORM =~ /darwin/ } do
   gem "my_osx_gem"
end

结束语

谢谢你的阅读并希望它能对你有所帮助,如果我有什么遗漏或你有什么问题的话请联系我~

你的 Gemfile 的源都会背要求设置为

"背" -> "被"

#1 楼 @justin 谢谢,已经修改过来了

感谢楼主翻译!

我发先平台真的非常有用

"发先" -> "发现"

#3 楼 @zhaowenchina 谢谢,已修改,以后打完了还要多检查两遍错别字,哈哈

]]> Greater Than ">1.0" 笔误。写得很好。

#5 楼 @gazeldx 谢谢,已修~

这篇文章又系统,又通俗,真是篇好文章。

还有一个小问题,有很多代码块中应该换行的地方写成了一行 类似下面这种 😄

group :development do
   gem "my_gem"  gem "my_other_gem"
end

#8 楼 @zhaowenchina 确实,是我从自己笔记本拷贝到这里来的时候没有注意,哥们你真细致。已修^_^

10 Floor has deleted

翻译的很清楚 :plus1:

#11 楼 @pathbox 谢谢肯定,若有不足,还望指正~

:plus1:

好文章

所以放入和当前 rails 环境

“所以”=>“所有”

感谢楼主的翻译!

#18 楼 @liukun_lk 感谢,已修~

入门好贴!

 好贴!

虽然用了 ruby 开发这么久,还是第一次了解到 Gemfile 的一些用法,已收藏~~

已收藏。

:plus1: 专门登录赞一个!

翻译得好,收藏了。

翻译的很棒,赞一个

赞~顶一个。

翻译的很好,非常详细~适合给 新手做教程~

赞,好文

楼主,请教个问题: 1) 新建的 rails 项目如果在 Gemfile 中只指定了需要的 gem,而不指定各 gem 版本,在执行 bundle install 时是怎么处理的?是直接使用本机已安装的 gem 版本 (若 gem 版本不止一个,会用哪个) 还是重新搜索安装网上最新的 gem 版本? 2) 若添加新的 gem,再次执行 bundle install, 之前已经安装的 gem 是按当时的版本被锁在了 Gemefile.lock 文件中,还是又重新安装最新的? 3) 如果想安装指定版本的 gem,在 Gemfile 中指定了版本,Gemefile.lock 中版本会自动变更吗?

我引入了一个 gem 想把他删除,但是删除了服务就报错没有引入这个 gem。。gem 有回滚的操作么? PS:gem 'weixin_rails_middleware' 写在 gemfile 里面

You need to Sign in before reply, if you don't have an account, please Sign up first.