构建一个 Ruby Gem 第五章 发布

在第二章中, 我们简要的看了一下默认的 bundler 给我们创建的 Rakefile:

require "bundler/gem_tasks"

这单独的一行可以让我们轻松的发布我们的 gem。如果我们在命令行输入 rake -T ,我们会看到下面的内容:

$ rake -T
rake build   # Build mega_lotto-0.0.1.gem into the pkg directory
rake install  # Build and install mega_lotto-0.0.1.gem into system gems
rake release  # Create tag v0.0.1 and build and push mega_lotto-0.0.1.gem
                       # to Rubygems

rake build 将会创建一个带版本号的 .gem 文件在我们的 gem 的 pkg/ 目录下, bundler 创建了一个 .gitignore 文件来把 pkg/ 目录排除到 git 版本控制之外。 所以即使在运行 rake build 命令后,被打包的 *.gem 文件也不会被提交到源码库。

rake install 顾名思义,将会把 gem 安装到本地。不过既然我们可以在一个Gemfile 中指定本地路径的gem (通过 path 选项),我没发现这个任务有什么有用的地方。

rake release 正如我们从命令的描述中看到的一样,它检查我们的 gem 的当前版本并且建议它要发布=v0.0.1= 版本,基于 lib/mega_lotto/version.rb 文件。

module MegaLotto
  VERSION = "0.0.1"
end

执行 rake release 命令将会做如下的事情:

  1. 构建我们的gem包并且把它放在 pkg/ 目录下
  2. 为当前版本(0.01)创建一个 git tag 并且把我们的改变推送到Github上
  3. 把我们打包好的gem推送到 Rubygems,让它能被公开访问

注意: 如果你不想开源你的 gem,=rake release= 目前不适合你。

源代码管理

虽然 git 不是唯一的源代码管理选择,它是 bundler 的内建工具并且可以轻松的和Github同步。 如果你没有选择其他工具的理由,git 和 Github 将会是我的源代码管理和远程仓库的选择。

rake release task 假定我们已经设置好了本地的git仓库并且和Github的远程仓库同步,所以让我们这样做把。

我们可以在 gem 的根目录运行 git init 。一旦初始化后,我们将会通过 git add 。 并且创建一个提交叫做 “Initial commit” 来暂存我们迄今为止的工作。

为了能远程同步,我们将会在 Github 上创建一个新的仓库。我们把它命名为 mega_lotto 并且配置它为公开仓库因为我们的目的是开源这个项目。 虽然仓库的名字不一定非要和gem的名字相符,但是取不同的名字会使人迷惑。对于剩下的选项,我们保持默认值。

现在远程仓库被创建好了,我们将要在我们的本地仓库加上引用。下面的命令就是为我们做这件事的:

$ git remote add origin [email protected]:brandonhilkert/mega_lotto.git

注意:远程 git 仓库的路径将会包含你的 Github 用户名和你的仓库的名字。要替换掉相应的内容。

现在我们的远程仓库已经被正确的配置了,我们将会推送我们的本地仓库的内容到 Github 使用命令:

$ git push -u origin master

今后,任何时候我们想要同步到 Github,使用缩写 git push 命令就可以了!

发布

让我们再次运行我们测试套件来确保事情都很顺利:

$ rake
....

Finished in 0.00401 seconds
4 examples, 0 failures

Randomized with seed 63009

测试通过,感觉良好。

注意:如果我们想要我们的 gem 被上传到 rubygems.org,我们首先要创建一个账号

运行 rake release 命令:

$ rake release
mega_lotto 0.0.1 built to pkg/mega_lotto-0.0.1.gem.
Tagged v0.0.1.
Pushed git commits and tags.
Pushed mega_lotto 0.0.1 to rubygems.org.

注意:Rubygems 不允许提交重复名字的 gems。因为我之前已经发布了名叫 MegaLotto 的gem,如果你照着上面的做法做的话会返回的一个rubygems的错误。

注意: Rubygems 要求身份验证,所以你在发布之前会被要求输入你的email和密码。

rake release 的结果会产生在这个页面上。正如你所看到的,它解析了 mega_lotto.gemspec 的内容并且加上了我的名字,照片,gem 的版本, 发布的日期和 gem的依赖。它也推送到github gem 的 release tag

下一次发布

假设我们想要让 mega_lotto gem 返回6个 integers 而不是5个。为了实现它,我们改变了 specs:

it "returns an array with 5 elements" do
  expect(draw.size).to eq(5)
end

改为:

it "returns an array with 6 elements" do
  expect(draw.size).to eq(6)
end

正如预期的,我们的测试套件现在失败了因为我们还没有升级我们的实现:

$ rake
..F.
Failures:
  1) MegaLotto::Drawing#draw returns an array with 6 elements
      Failure/Error: expect(draw.size).to eq(6)

         expected: 6          
              got: 5

        (compared using ==)
      # ./spec/mega_lotto/drawing_spec.rb:13

Finished in 0.00325 seconds
4 examples, 1 failure

Failed examples:

rspec ./spec/mega_lotto/drawing_spec.rb:12
# MegaLotto::Drawing#draw returns an array with 6 elements
Randomized with seed 8922

现在让我们改变我们的实现:

module MegaLotto
  class Drawing
    def draw
      # This value used to be 5
      6.times.map { single_draw }
    end
    private
    def single_draw
      rand(0...60)
    end
  end
end

并且运行测试再次确认:

$ rake
....

Finished in 0.00482 seconds
4 examples, 0 failures

Randomized with seed 14164

很好!让我们继续前进… 通常来说,我们将会创建一个紧随其后的提交来 bumping lib/mega_lotto/version.rb 文件里的版本号常量:

module MegaLotto
  VERSION = "0.0.2"
end

注意:我们在下一章会讨论的,推荐遵从语义化的版本控制。通过改变 mega_lotto gem 返回 6 个数字而不是 5 个,我们改变了实现。 如果我们拘泥于遵从语义化版本控制,我们应该发布一个主版本号而不是一个补丁。做实验并没有什么错只要你让用户知晓。 最好的方式就是说明这个 gem 正在开发中。

开发 public API 多半就是不断改变版本。通常来说,这适用于版本小于 1.0 的情况,这和项目相应变化。 现在我们的新版本已经准备好要发布了,让我们再次快速看看 rake 命令的说明:

$ rake -T
rake build   # Build mega_lotto-0.0.2.gem into the pkg directory
rake install  # Build and install mega_lotto-0.0.2.gem into system gems
rake release  # Create tag v0.0.2 and build and push mega_lotto-0.0.2.gem
                        # to Rubygems

正如我们看到的,rake 命令知道我们将要发布 0.0.2 版本 (因为我们 bumped 了 lib/mega_lotto/version 的VERSION 常量)。所以我们还在等什么呢???发布吧!

$ rake release
mega_lotto 0.0.2 built to pkg/mega_lotto-0.0.2.gem.
Tagged v0.0.2.
Pushed git commits and tags.
Pushed mega_lotto 0.0.2 to rubygems.org.

我们的新版本会在 Rubygems landing page 和 the Github releases page 被显示出来。

何时发布

很不幸,没有关于何时发布方面的规则。然而,你大概注意到了,在大型软件项目中 (Google Chrome, Mozilla Firefox 等等),更小更频繁的发布是主流方式。 语义化版本号已经创建了一个可信赖的模式来发布可预测的软件。

我非常确定我同意 “有 bug 就发布一个 fix” 的说法 (但是我们都写没 bug 的代码,不是吗?!?!)

除此以外,你将不得不平衡多少特性或者改变你一次想要发布。并且这会,几乎一定,在项目的生命周期中不断变化。在开发初期,预期会有大量的改变是合理的。虽然有一些不太可靠。然而,随着项目的成熟和越来越多的人依赖它,我的建议是更频繁的发布小的改变。

总结

从启动项目到提供发布任务,在我们的 gem 开发过程中 bundler 是一个必不可少的工具。不必担心元数据或在 git 中应用正确的版本号,bundler 出现在需要它的地方。 在下一章,我们会近距离看看语义化版本号并学习一个可预测版本系统所带来的好处。我们也会看到版本在管理 gem 依赖中扮演的角色。