Ruby Ruby 基础 - RubyGem,如何开发一个自己的 Gem

oneapm · 2015年07月02日 · 最后由 cicholgricenchos 回复于 2015年07月03日 · 6484 次阅读

什么是 RubyGem

RubyGem 是 Ruby 语言的标准源码打包格式。

大家一直都在用gem这个命令,但是很少有人知道这个东西是怎么来的,这里我从网上扒下一些资料汇总一下,分享给大家。最后面会有这些链接,想进一步了解的,可以点进去看看。Ruby 语言深受其他几种脚本语言的影响,其中就有 Perl,而 Perl 有个 CPAN(Comprehensive Perl Archive Network),这个东西也就像是现在的 RubyGems.org,但是当时 Ruby 是没有这样一个东西的。像 CPAN 和 RubyGem,它们仅仅是定义好的一种源码的打包和安装方式,另外还有一些组织,会提供这种免费的公共的源码包的存储,这也就现在大家每天都要使用的安装方式:

gem install rails

在 RubyGem 的发展历史当中,有几位重要的人物,这里也作为八卦知识给大家晒一晒,就当做大家茶余饭后的谈点吧。Ruby 社区的人应该都知道 Jim Weirich 这个人,他已经在 2014 年 2 月份去世了,是一个可爱的白胡子大叔,他和另外的四位 Rich Kilmer, Chad Fowler, David Black, Paul Brannan 在 2003 年制定了 RubyGem 的基本规范和实现,但是其实 RubyGem 最早是 Ryan Leavengood 在 2001 年开发的,可惜没有流行起来,最后到了 2003 年,前面的 5 个人经过 Ryan Leavengood 的同意,使用了 RubyGem 这个名称,开发了新版的 RubyGem,其中并没有使用 Ryan Leavengood 的代码。这里附上 rubygems 的执行文件链接,看看注释,里面有上面几个人的名字 rubygems/blob/master/bin/gem

rubygems 有默认的源,也可以更改,国内的基本就是https://rubygems.taobao.org了,有些公司有自己的需求,也会搭建自己的私源。当前的官方源为https://rubygems.org,这个源也是几经辗转,早期的 Ruby 用户都知道http://gems.rubyforge.orghttp://gemcutter.org,甚至 github 都曾经作为源使用过,也就是http://gems.github.com,这三个现在都已经弃用了。

看看,一个简单的gem install历史还不少啊。

RubyGem 的基本使用方法

gem install rails  //安装rails
gem install rails -v 4.2.0   //安装指定版本的rails
gem search rails  //查找所有名称中含有rails的gem
gem search ^rails  //查找所有名称中以rails开头的gem
gem search ^rails -d  //查找所有名称中以rails开头的gem,并显示描述
gem build package.gemspec  //构建一个gem,就是把你自己写的gem源码,打包成一个.gem文件
gem push pack-1.0.gem  //发布到源上,默认是rubygems.org

这里只是简单列出了最常用的使用方法,大家看看就好,命令很有限,也很简单,执行gem --help,基本上所有的东西你都能 10 分钟内学会。

如何制作一个自己的 RubyGem

前几年还是有这样那样的工具,现在用bundler就够了。

$ bundler gem mygem
      create  mygem/Gemfile
      create  mygem/Rakefile
      create  mygem/LICENSE.txt
      create  mygem/README.md
      create  mygem/.gitignore
      create  mygem/mygem.gemspec
      create  mygem/lib/mygem.rb
      create  mygem/lib/mygem/version.rb
Initializing git repo in /home/lizhe/Workspace/mygem

一个 bundler 命令就搞定了。来看看 mygem 这个文件夹下的东西:

total 24
-rw-rw-r-- 1 lizhe lizhe   90  7月  2 15:52 Gemfile
drwxrwxr-x 3 lizhe lizhe 4096  7月  2 15:52 lib
-rw-rw-r-- 1 lizhe lizhe 1062  7月  2 15:52 LICENSE.txt
-rw-rw-r-- 1 lizhe lizhe  850  7月  2 15:52 mygem.gemspec
-rw-rw-r-- 1 lizhe lizhe   29  7月  2 15:52 Rakefile
-rw-rw-r-- 1 lizhe lizhe  556  7月  2 15:52 README.md

现在来看看 gemspec 这个文件,它描述了这个 Gem 的各种信息

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'mygem/version'

Gem::Specification.new do |spec|
  spec.name          = "mygem"
  spec.version       = Mygem::VERSION
  spec.authors       = ["lizhe"]
  spec.email         = ["[email protected]"]
  spec.summary       = %q{TODO: Write a short summary. Required.}
  spec.description   = %q{TODO: Write a longer description. Optional.}
  spec.homepage      = ""
  spec.license       = "MIT"

  spec.files         = `git ls-files -z`.split("\x0")
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.7"
  spec.add_development_dependency "rake", "~> 10.0"
end

我发现有人看到这个文件的内容时,倒是关心那个'git ls-files -z'.split("\x0")是什么意思?以及那个\x0是什么?附上一个链接,解释一下,参考链接。这个文件最上面先把 lib 文件夹添加到 load path 中,Gem::Specification 的第一部分主要是描述这个 gem 的信息,包括名称,版本等等,第二部分是这个 gem 都包括哪些文件,执行文件,测试文件以及哪些路径下的文件可以添加到 load path 中。第三部分是开发 mygem 需要依赖的其他 gem。这些信息都可以自定义,先按照默认走。让我们 build 第一个 gem 吧。

$ rake build

rake aborted!
WARNING:  See http://guides.rubygems.org/specification-reference/ for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    "FIXME" or "TODO" is not a description
/home/lizhe/.rvm/gems/ruby-2.1.5@global/gems/bundler-1.7.12/lib/bundler/gem_helper.rb:149:in `sh'
/home/lizhe/.rvm/gems/ruby-2.1.5@global/gems/bundler-1.7.12/lib/bundler/gem_helper.rb:57:in `build_gem'
/home/lizhe/.rvm/gems/ruby-2.1.5@global/gems/bundler-1.7.12/lib/bundler/gem_helper.rb:39:in `block in install'
/home/lizhe/.rvm/gems/ruby-2.1.5@global/bin/ruby_executable_hooks:15:in `eval'
/home/lizhe/.rvm/gems/ruby-2.1.5@global/bin/ruby_executable_hooks:15:in `<main>'
Tasks: TOP => build
(See full trace by running task with --trace)

这个警告是提醒我们需要替换 gemspec 中的FIXMETODO,这个警告如果不解决是无法 build 一个 gem 的,直接删除掉 summary 和 description 中的TODO

spec.summary       = %q{Write a short summary. Required.}
spec.description   = %q{Write a longer description. Optional.}

再来执行:

$ rake build

mygem 0.0.1 built to pkg/mygem-0.0.1.gem.

好了,第一个 gem 诞生了。它就在当前目录的 pkg 下:mygem-0.0.1.gem。如何使用呢?不考虑 bundler 的情况下,如果你开起了一个 irb 或者 pry 的 session 时,一般都会这样写:require "mygem",如果你现在这样做,那肯定不行,因为它还没有被安装到 ruby 的 load path 中,那就把它安装上。

$ rake install

mygem 0.0.1 built to pkg/mygem-0.0.1.gem.
mygem (0.0.1) installed.

安装好了,那就来使用一下,打开 irb:

require "mygem"
=> true
Mygem
=> Mygem

看,已经可以使用这个 module 了,不过这个 gem 啥也干不了,那么我们就给它添加一个方法吧,打开 lib/mygem.rb,添加一个方法:

require "mygem/version"

module Mygem
  def self.hello
    p "hello from my gem"
  end
end

保存,然后执行rake install,这个命令会先 build 然后 install,再重新打开 irb:

require "mygem"
=> true
Mygem.hello
=> "hello from my gem"

能够正常运行了,那就来发布第一个 gem 吧:

rake release
// 输入你在rubygems.org上的账号和密码

如果你的一个 rails 应用正好需要输出一个hello from my gem,那么现在你可以在 Gemfile 中添加这个 gem 了:

gem 'mygem'

添加完执行bundle install,你就可以在你的 rails 应用中使用它了。😄

下一节:Ruby 基础 - RubyGem,如何测试 Gem

参考链接:


本文由OneAPM工程师原创,欢迎大家来OneAPM做客,共同讨论各种技术问题,OneAPM提供包括Ruby在内的主流 6 种编程语言,以及浏览器端、移动端、服务器软硬件环境的性能监测服务。

太基础了吧,wiki 有的

说到 gem 我倒是刚遇到一个问题,空的文件夹没办法加到 gem 的 files 里,本来可以拷贝的一个目录结构,现在不得不一句一句 mkdir

@cicholgricenchos 你的问题属于 git 的问题吧。 在空文件夹里创建一个零字节的文件就成。 按惯例一般是.gitkeep

写的不错,做一个 gem 不难。 如何运行测试呢?

#1 楼 @i5ting 这个就写给入门级的同学看的哈,😄, 主要是想从基础入手阐述 Ruby 中的几个基础概念,后续还会结合加载路径和 bundler 写两篇

#4 楼 @chenge 好的,下一篇就写一下如何测试一个 gem,敬请关注啊,😄

#6 楼 @oneapm根目录下,ruby test/test_mygem.rb会报错helper找不到。不知道是不是bug?

#7 楼 @chenge 你用的是 minitest 吧,如果是 minitest,它不会解决加载路径的问题,所以require 'test_helper'会找不到路径,需要用request_relative '../test_helper'的相对路径方法。如果用的是 rspec,它是会默认先把 spec 文件夹放到加载路径下的。其实有更简单的方法,就是在 Rakefile 中添加一个 test task:

require 'rake/testtask'

Rake::TestTask.new do |t|
  t.libs << 'test' << 'lib'
  t.pattern = 'test/test_*.rb'
end

然后执行rake test就可以了。其实还可以更简单,现在你的每个文件还是要require 'test_helper'的,它可以默认就被加载进去,也就每个文件自动require 'test_helper',只需要一句话:

require 'rake/testtask'

Rake::TestTask.new do |t|
  t.libs << 'test' << 'lib'
  t.pattern = 'test/test_*.rb'
  t.ruby_opts << '-r test_helper' # 指定ruby运行参数,自动require test_helper
end

# 再指定默认的task为test
task :default => :test

现在你的每个测试用例文件都不需要 require 什么东西了,只需要在 test_helper 中设置好就 OK 了,执行rake

$ rake

Run options: --seed 12023

# Running:

.

Finished in 0.000722s, 1385.6922 runs/s, 1385.6922 assertions/s.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

现在执行测试是不是简单多了?😄

赞一个!!!

#3 楼 @blueplanet 和 git 并没有关系啊,不过为了保持目录结构而在每个空目录建文件,我还不如 mkdir...

oneapm Ruby 基础 - RubyGem,如何测试 Gem 提及了此话题。 09月28日 00:52
Kirisames Ruby Gem 打包发布问题 提及了此话题。 03月07日 16:59
Kirisames Ruby Gem 打包发布问题(多文件模块调用) 提及了此话题。 03月07日 22:38
需要 登录 后方可回复, 如果你还没有账号请 注册新账号