tjinjin's blog

さらなる高みに1nmずつ近づくブログ

gemの作り方を学ぶ

About

chefやServerspecなどを使うようになって、ここの部分の動きってどうなってるんだろう?とか、こういう機能あったら便利だっていうときにはgemのソースを見たり・改造したりしたいと思うことが増えて来ました。ここでgemってそもそもどうやって作るんだろうと気になったので、まとめてみたいと思います。

リポジトリを作る

適当なディレクトリでgemを作成するコマンドを叩く

$ bundle gem test-gem -t
      create  test-gem/Gemfile
      create  test-gem/Rakefile
      create  test-gem/LICENSE.txt
      create  test-gem/README.md
      create  test-gem/.gitignore
      create  test-gem/test-gem.gemspec
      create  test-gem/lib/test/gem.rb
      create  test-gem/lib/test/gem/version.rb
      create  test-gem/.rspec
      create  test-gem/spec/spec_helper.rb
      create  test-gem/spec/test/gem_spec.rb
      create  test-gem/.travis.yml

commitしてリモートリポジトリに登録する

$ cd test-gem
$ git commit -m "Initial commit"
$ hub create
$ git push origin master

hubコマンドが入っているとCLIからリモートリポジトリを作成できるので簡単です。

Gemを開発する

gem.specにmeta情報を記述する

自動で下記のように作成されるので、最低限埋めます。

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

Gem::Specification.new do |spec|
  spec.name          = "test-gem"
  spec.version       = Test::Gem::VERSION
  spec.authors       = ["krossblack777"]
  spec.email         = ["krossprogram@gmail.com"]
  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"
  spec.add_development_dependency "rspec"
end

下記3点を最低限編集すればいいようです。

  spec.summary       = %q{test}
  spec.description   = %q{test}
  spec.homepage      = "https://github.com/krossblack777/test-gem"

開発する

今回はhello world!!!と出力するだけの簡単なものです。

require "test/gem/version"

module Test
  def hello
  'hello world!!!'
  end
end

メソッドを呼び出すとhello worldと表示されるはずです。

$ bundle install
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Installing rake 10.4.2
Using bundler 1.7.11
Installing diff-lcs 1.2.5
Installing rspec-support 3.3.0
Installing rspec-core 3.3.2
Installing rspec-expectations 3.3.1
Installing rspec-mocks 3.3.2
Installing rspec 3.3.0
Using test-gem 0.0.1 from source at .
Your bundle is complete!
It was installed into ./work
$ bundle list
Gems included by the bundle:
  * bundler (1.7.11)
  * diff-lcs (1.2.5)
  * rake (10.4.2)
  * rspec (3.3.0)
  * rspec-core (3.3.2)
  * rspec-expectations (3.3.1)
  * rspec-mocks (3.3.2)
  * rspec-support (3.3.0)
  * test-gem (0.0.1)

bundle installの時にtest-gemはfrom source at .と表示されていて、ローカルのソースを指定しているようです。これでtest-gemが追加されました。実際に使ってみます。

$ be irb
irb(main):001:0> require 'test/gem'
=> true
irb(main):002:0> Test
=> Test
irb(main):003:0> Test.hello
=> "hello world!!!"

問題なさそうです。これで開発が完了しました。

gemをパッケージ化する

パッケージングはrakeが定義されているので、それを使います。定義されているタスクを確認すると

$ bundle exec  rake -T
rake build    # Build test-gem-0.0.1.gem into the pkg directory
rake install  # Build and install test-gem-0.0.1.gem into system gems
rake release  # Create tag v0.0.1 and build and push test-gem-0.0.1.gem to Rubygems
rake spec     # Run RSpec code examples

この中でrake buildを実行すればいいはずです。

$ bundle exec rake build
test-gem 0.0.1 built to pkg/test-gem-0.0.1.gem.
$ ls pkg/
test-gem-0.0.1.gem

これでpackage化したgemができました。

作ったパッケージを使ってみる

やり方がよくわからなかったですが、一応できたので。

$ gem unpack test-gem-0.0.1.gem
$ cat Gemfile
# A sample Gemfile
source "https://rubygems.org"

# gem "rails"
gem "test-gem", :path => "~/work/test/test-gem-0.0.1"

これでインストールの準備ができました。

$ bundle install
irb(main):001:0> require 'test/gem'
=> true
irb(main):002:0> Test.hello
=> "hello world!!!"

うまく実行できました。インストールの仕方は何通りかあるみたいです。bundlerを使わない方法はうまく行きませんでした。

gemを登録する

rakeを実行すればいいみたいです(試してない)。

$ rake release

終わりに

これでgemが作れるようになっちゃいましたね!後は作る能力さえつければいいんだ…!!

参考

番外編

作ったgemがirbで実行できない。

gemコマンド使って作ったpackageからインストールします

$ gem install -l pkg/test-gem-0.0.1.gem

-lでローカルのパッケージを指定しています。

$ ruby -e "require 'test/gem';puts Test.hello"
hello world!!!

上記のように実行できるのですが、irbだと

$ irb
irb(main):001:0> require 'test/gem'
LoadError: cannot load such file -- test/gem

irb(main):002:0> Gem.path
=> ["~/work/ruby/2.1.0"]

となり、変な所にPATHが設定されているようです(謎)