22 October 2014

前记

从上班的第一天,前辈给我装好了环境(git clone & bundle install),就开始使用bundle。现在,想要进行深入的了解一下。

简介

bundle是Ruby Dependency Management,为Ruby项目提供了简单方便的环境,其可用来追踪和安装所需gems包的精确版本。Bundle带你远离依赖地狱

使用很简单,首先gem install bundler,然后编写Gemfile,bundle install安装并生成Gemfile.lock, 添加文件到版本控制中。

bundle文档

命令模式: bundle COMMAND [–no-color] [–verbose] [ARGS]

选项:

--no-color   Prints all output without color
--verbose    Prints out additional logging information

bundle命令可以简单分为基础命令和工具命令。基础命令包括: install, update, package, exec, config, help; 工具命令包括: check, list, show, outdated, console, open, viz, init, gem, platform, clean。具体介绍如下:

基础命令:

  • bundle install 安装通过Gemfile或Gemfile.lock指定的gem包
  • bundle update 将依赖的gem包更新到最新Update dependencies to their latest versions
  • bundle package 将项目所需的gem文件打包到vendor/cache目录
  • bundle exec 在当前bundle的环境中执行脚本,现在好像默认就是在bundle环境中执行脚本。
  • bundle config 为bundler指定并读取配置选项
  • bundle help 显示每个命令的帮助的细节

工具命令:

  • bundle check 确认应用程序的需求是否已经安装或可获得
  • bundle list 显示当前bundle中所有的gem包
  • bundle show 显示特定gem包源码的位置,具体的使用像bundle show rack,然后显示gem包安装的位置
  • bundle outdated 显示当前bundle中所有的过时的gem包
  • bundle console 在当前的bundle环境中启动一个IRB会话(考虑Rails的控制台)
  • bundle open 在编辑器中打开一个已安装的gem
  • bundle viz 生成版本依赖关系的可视化表示(需要ruby-graphviz包)
  • bundle init 生成一个简单的Gemfile,并将其放在当前目录下
  • bundle gem 创建一个简单的gem,特别适合开发时使用bundler
  • bundle platform 显示平台兼容性信息
  • bundle clean 清除在bunlder中没有使用的gem包

Gemfile文档

Gemfile是一种描述Ruby程序gem包依赖的格式, 描述了与执行代码相关的gem包依赖。一般放置在项目根目录下,例如,在Rails程序中,Gemfile放置在同Rakefile相同的目录层次下。

Gemfile被看作Ruby代码来求值,其中,存在很多可用的方法来描述gem包的依赖。这一点很重要,Gemfile其实就是Ruby的用来描述版本依赖关系的DSL。

全局源(#source)

在Gemfile的顶部,添加包含Rubygems源的地址。

source "https://rubygems.org"

在bundler 1.7中,可以但不推荐,在Gemfile的顶部,添加多个有效的全局源(每个都必须是有效的Rubygems源码库)。

Sources are checked for gems following the heuristics described in SOURCE PRIORITY. If a gem is found in more than one global source, Bundler will print a warning after installing the gem indicating which source was used, and listing the other sources where the gem is available. A specific source can be selected for gems that need to use a non-standard repository, suppressing this warning, by using the :source option or a source block.

证书(credentials)

Some gem sources require a username and password. Use bundle config to set the username and password for any sources that need it. The command must be run once on each computer that will install the Gemfile, but this keeps the credentials from being stored in plain text in version control.

有些gem源需要用户名和密码,可以使用bundle config设置用户名和密码。在需要使用Gemfile安装每台机器上,都要运行该命令,然后保持证书存储在受版本控制的单个文件中。

bundle config https://gems.example.com/ user:password

对于某些源,可以在Gemfile的源URL中包含证书信息。

source "https://user:password@gems.example.com"

在源URL中指定的验证要比使用配置的优先级高。

ruby

If your application requires a specific Ruby version or engine, specify your requirements using the ruby method, with the following arguments. All parameters are OPTIONAL unless otherwise specified.

如果应用程序需要一个特定的Ruby版本或engine,可以使用带有如下参数ruby方法来指定。除了版本,其他参数都是可选的。

The version of Ruby that your application requires. If your application requires an alternate Ruby engine, such as JRuby or Rubinius, this should be the Ruby version that the engine is compatible with.

应用程序所需的Ruby版本是必须,如果应用程序需要一个可选的Ruby引擎,比如JRuby或Rubinius,要注意Ruby版本和engine的兼容性。

ruby "1.9.3"

Each application may specify a Ruby engine. If an engine is specified, an engine version must also be specified.

每个应用程序都可以指定一个Ruby版本。如果指定engine,那么engine的版本也需要被指定,需要注意的是,engine版本必须和Ruby版本想匹配。

ruby "1.8.7", :engine => "jruby", :engine_version => "1.6.7"

应用程序可能需要指定Ruby的patchlevel(同Ruby开发和发布的版本有关系)。

ruby “2.0.0”, :patchlevel => “247”

gems

指定gem依赖使用gem方法,并带有如下的参数,name, version, require, groups, platforms, source, git, github, path 除了gem名,其他参数都是可选的。

对于每个gem包依赖,列出独立的一行:

gem "nokogiri"

版本: 每个Gem包都会有一个或多个版本指定的语句:

gem "nokogiri", ">= 1.4.2"
gem "RedCloth", ">= 4.1.0", "< 4.2.0"

require参数: Each gem MAY specify files that should be used when autorequiring via Bundler.require. You may pass an array with multiple files or true if file you want required has same name as gem or false to prevent any file from being autorequired.

gem "redis", :require => ["redis/connection/hiredis", "redis"]
gem "webmock", :require => false
gem "debugger", :require => true

require参数默认被设定为gem包的名字,例如,如下的表述是相同的:

gem "nokogiri"
gem "nokogiri", :require => "nokogiri"
gem "nokogiri", :require => true

分组(:group或:groups): 每个gem包都会指定一个或多个分组。不指定任何分组的gem都会被放置到默认的分组中。

gem "rspec", :group => :test
gem "wirble", :groups => [:development, :test]

Bundler运行时使用两个主要的方法(Bundler.setup和Bundler.require), 从而限制其对特定分组的影响。

# setup添加gems到Ruby的加载路径中
Bundler.setup                    # defaults to all groups
require "bundler/setup"          # same as Bundler.setup
Bundler.setup(:default)          # only set up the _default_ group
Bundler.setup(:test)             # only set up the _test_ group (but `not` _default_)
Bundler.setup(:default, :test)   # set up the _default_ and _test_ groups, but no others

# require加载所有在特定分组的gem包
Bundler.require                  # defaults to just the _default_ group
Bundler.require(:default)        # identical
Bundler.require(:default, :test) # requires the _default_ and _test_ groups
Bundler.require(:test)           # requires just the _test_ group

Bundler命令行接口允许在bundle install时,使用--without不安装某些分组的gem。可使用空格分隔多个需要忽略的分组。

bundle install --without test
bundle install --without development test

在运行bundle install --without test之后,bundler将记得在上次安装中,排除了test分组。下次调用bundler install安装时,将自动带有--without选项。

Also, calling Bundler.setup with no parameters, or calling require “bundler/setup” will setup all groups except for the ones you excluded via –without (since they are obviously not available).

此外,调用Bundler.setup不带参任何参数,或者调用require "bundler/setup"将启动所有的分组(除了那些使用without排除的分组)。

注意: 使用bundle install时,bundler下载并对所有gems进行求值,从而创建一个单一的权威的gem包和依赖关系的列表。这表明,不能在不同分组中设置同一gem包的不同版本。更多信息,参考Understanding Bundler.

平台(:platforms): 如果某个gem包只能用在某一或某些平台上,可以使用:platforms来指定。除了没有--without选项,平台和分组类似。下面是Gemfile平台的列表:

ruby C        Ruby (MRI) or Rubinius, but NOT Windows
ruby_18       ruby AND version 1.8
ruby_19       ruby AND version 1.9
ruby_20       ruby AND version 2.0
ruby_21       ruby AND version 2.1
mri           Same as ruby, but not Rubinius
mri_18        mri AND version 1.8
mri_19        mri AND version 1.9
mri_20        mri AND version 2.0
mri_21        mri AND version 2.1
rbx           Same as ruby, but only Rubinius (not MRI) 
jruby         JRuby
mswin         Windows
mingw         Windows 32 bit 'mingw32' platform (aka RubyInstaller)
mingw_18      mingw AND version 1.8
mingw_19      mingw AND version 1.9
mingw_20      mingw AND version 2.0
mingw_21      mingw AND version 2.1
x64_mingw     Windows 64 bit 'mingw32' platform (aka RubyInstaller x64)
x64_mingw_20  x64_mingw AND version 2.0
x64_mingw_21  x64_mingw AND version 2.1

与分组类似,可以指定一个或多个平台:

gem "weakling",   :platforms => :jruby
gem "ruby-debug", :platforms => :mri_18
gem "nokogiri",   :platforms => [:mri_18, :jruby]

如果分组不匹配当前的平台,所有涉及分组的操作(bundle install, Bundler.setup, Bundler.require)都是类似的。

源(:source): 通过’:source’选项,可为某个gem设置可选的Rubygems代码库。例如:

gem "some_internal_gem", :source => "https://gems.example.com"

上述命令强制从特定的源中加载gem,并忽略申明在文件顶层的全局源。如果源中不存在,gem包就不会被安装。

Bundler will search for child dependencies of this gem by first looking in the source selected for the parent, but if they are not found there, it will fall back on global sources using the ordering described in SOURCE PRIORITY.

在搜索带有source选项的gem包的子依赖时,Bundler首先在父目录的源中搜索。如果没能找到,按源的优先级顺序在全局源中进行搜索。

Selecting a specific source repository this way also suppresses the ambiguous gem warning described above in GLOBAL SOURCES (#source).

按这种方式选择特定的源码库,将会抑制在全局源中的gem包的二异性。

GIT(:git): 如果必要的话,可以使用git选项指定位于特定git版本库中的gem。版本库可以是公有的 http://github.com/rails/rails.git 或私有的 git@github.com:rails/rails.git 。如果版本库是私有的,使用bundle install的用户,必须在其$HOME/.ssh存在相应的key。

Git repositories are specified using the :git parameter. The group, platforms, and require options are available and behave exactly the same as they would for a normal gem.

git版本库通过:git参数指定,分组,平台以及require选项都是可用的,并且其行为同常规的gem包类似。

gem "rails", :git => "git://github.com/rails/rails.git"

A git repository SHOULD have at least one file, at the root of the directory containing the gem, with the extension .gemspec. This file MUST contain a valid gem specification, as expected by the gem build command.

git代码库中的项目,必须在根目录下包含扩展名为.gemspec的文件,文件中必须包含有效的gem规格描述(可使用gem build运行)。

如果git代码库中不包含.gemspec, bundler将尝试创建一个。但其中并不包含任何依赖关系,执行描述或C扩展编译指令,所以就不能恰当的整合到应用程序中。

如果.gemspec文件中本身指定了版本号,而在gem参数中有指定了版本参数。但这两者不匹配时,bundler将会打印警告。

gem "rails", "2.3.8", :git => "git://github.com/rails/rails.git"
# bundle install will fail, because the .gemspec in the rails
# repository's master branch specifies version 3.0.0

如果git代码库中不包含.gemspec,就必须要提供版本号,否则bundler将使用自己创建的.gemspec文件中的版本号。Git版本库支持一系列的附加选项,比如:

  • branch, tag和ref - 这些选项是互斥的,默认的是:branch => "master"
  • submodules - 设定:submodules => true时,将会扩展任何包含在git版本库中子模块

如果git版本库包含了多个.gemspecs,每个位于文件系统中的.gemspecs表示一个gem包。

|~rails                   [git root]
| |-rails.gemspec         [rails gem located here]
|~actionpack
| |-actionpack.gemspec    [actionpack gem located here]
|~activesupport
| |-activesupport.gemspec [activesupport gem located here]
|...

要安装一个位于git版本库中的gem,bundler进入包含gemspec的目录,运行gem build name.gemspec,然后安装目标gem。gem build命令随着Rubygems一起发布,并在.gemspec所处的目录下,对.gemspec文件进行求值。

GITHUB (:github): 如果使用git代码库位于Github并且是公共的,可以使用:github缩写形式来指定github用户名和版本库名(不需要”.git”结尾,并以/分隔),如果用户名和代码库相同,可忽略其中一个。示例如下:

gem "rails", :github => "rails/rails"
gem "rails", :github => "rails"

上述例子,等价于如下的描述:

gem "rails", :git => "git://github.com/rails/rails.git"

此外,如果想要选择一个特定的分支,示例如下:

gem "rails", :github => "rails/rails", :branch => "branch_name"

路径(:path): 可以指定位于文件系统特定目录下的gem包,相对路径是相对于gemfile的路径。类似:git选项的语义,:path选项需要对应的目录包含.gemspec或者显式指定特定版本的gem。但是,bundler不自动编译:path选项指定的C扩展。

gem "rails", :path => "vendor/rails"

源,git,路径,分组以及平台的代码块

:source, :git, :path, :group以及:platforms选项都可接受代码块。

source "https://gems.example.com" do
  gem "some_internal_gem"
  gem "another_internal_gem"
end

git "git://github.com/rails/rails.git" do
  gem "activesupport"
  gem "actionpack"
end

platforms :ruby do
  gem "ruby-debug"
  gem "sqlite3"
end

group :development do
  gem "wirble"
  gem "faker"
end

In the case of the git block form, the :ref, :branch, :tag, and :submodules options may be passed to the git method, and all gems in the block will inherit those options.

在git的代码块中,可以设置:ref, :branch, :tag以及:submodules等选项,在代码块中的所有gem都会会继承这些选项。

GEMSPEC (#gemspec)

如果想要使用bundler来辅助安装正在开发中的gem包的依赖关系,可以使用gemspec方法,将所有的依赖管理放置在.gemspec文件中。

gemspec方法将将运行时依赖关系看作默认分组中的gem requirements,并添加开发依赖到development分组中。最后,对应用程序中添加gem requirement(:path => ‘.’). 在与Bundler.setup连接时,可以在测试代码中,就想对已安装的gem包那样,require相应的项目文件,而不需要手动操作加载路径,或者通过相对路径require项目文件。

gemspec方法支持可选的:path, :name以及:development_group选项,从而控制bundler查找.gemspec的路径,使用的.gemspec的命名,以及包含的开发依赖分组。

SOURCE PRIORITY

在尝试定位gem包,从而满足gem版本需求时,bundler使用如下的优先级顺序:

  1. 显式附加到gem包上的源,比如使用:source, :path或者:git选项
  2. 对于隐式的包(显式gem包的依赖箱),优先使用父gem包的source,git或path选项。这意味着bundler优先使用Rails的git库中的ActiveSupport,而不是rubygems.org中的
  3. 在全局源中指定的文件行,搜索顺序是从最后添加到最先添加的

Gemfile中,版本控制选项中,可以使用’1.2’, ‘~> 1.2.1’, ‘=> 0.4’之类的表述。

简单的Bundler工作流程

创建Rails项目时,已经带有一个Gemfile。而对于Sinatra应用程序,运行bundle init创建一个简单的Gemfile文件,其内容大体如下:

# A sample Gemfile
source "https://rubygems.org"

# gem "rails

接下来,就是添加应用程序的依赖选项,要注意一下使用合适版本的gem包。添加完应用程序的版本依赖后,使用bundle install安装所依赖的相应的Gem包。如果安装过程中,Gemfile和Gemfile.lock发生了冲突,运行bundle update xxx更新发生冲突的Gem包。如果想要更新所有Gemfile中所有的Gem包,运行bundle update

将Gemfile.lock添加到版本控制系统中,从而保存程序可运行的精确的版本。在部署应用程序时,使用bundle install --deployment

简单来说,整个流程是:bundle initbundle installbundle update [xxx]bundle install --deployment

后来,发现,bundle 可以像node 一样,将 gem 包 lock 到本地,然后通过 bundle --local 进行安装。

后记

花了些时间,看完bundle之后,确实获得了一些新的认识。毕竟,之前只会用bundle install而已。无论是,Gemfile还是Rakefile,都是Ruby的内部DSL,这样来想,应该比较好理解一些。虽然,最近写过一个Rake任务,实际上对其认识不是特别深,需要更进一步的理解。

类似xxxfile的这样的文件,现在接触到的有:Gemfile,Rakefile,Guardfile(与Guard有关),Procfile,其中,后两个完全不知道干嘛的。




傲娇的使用Disqus