BundlerでC拡張を含んだgemを公開する

はじめに

先日、immutable_list というgemを公開したのですが、 思っていたよりも簡単に rubygems.org に登録することができて感動しました。

gemを作るには、色々な方法があるようですが、最近だとBundler を使う方法が一番シンプルで良さそうです。

RubyGems に gem を登録するためには、gitのリモートリポジトリが必要ですが、ここでは GitHub を利用します。

あと、よく質問されるのですが、RubyGems に登録するために審査は一切ありません。

Tips: gemの命名方法

本題とはズレますが、gemの命名にはルールがあるので軽く紹介します。

gemname には、小文字のアルファベット、数字、ハイフン、アンダースコア、ドットが使用可能のようです。

ハイフンとアンダースコアは次のように使い分けることが推奨されています。

  • - (ハイフン) : パスの区切り
  • _ (アンダースコア) : 単語の区切り

普通のgemを公開する場合

C拡張を含まない、Rubyで実装したライブラリをgemにする場合は、とても簡単です。

はじめてbundlerでruby gem作ってgithubとrubygemsに上げてみた という記事が分かりやすかったので、 Rubyで実装したライブラリをgemにする場合はこちらを参考にすれば良いと思います。

基本的には、次のような流れになります。

  1. $ bundle gem <gemname> でgemを作るために必要なファイルの雛形の一式が作られるので、細かい部分を編集する。

    • $ cd <gemname>>
    • lib/<gemname>.rb にライブラリの本体を実装する。
    • lib/<gemname>/version.rb でバージョンを指定する。
    • <gemname>.gemspec のTODOを埋める。
  2. gemのパッケージを作成

    • $ gem build <gemname>.gemspec
    • $ rake install で動作を念のためにテスト。
  3. GitHub に push

    • ファイル一式をGitHubに登録する。(普通のgitのリポジトリを作るのと同じなので詳しい説明は省きます。)
  4. RubyGems に push

    • $ gem push pkg/<gemname>>-x.x.x.gem
    • これで、世界中の誰からでも $ gem install <gemname> で利用可能になる。
  5. バージョンアップ

    • lib/<gemname>/version.rb を編集。
    • $ rake release で RubyGems と GitHub の両方に push してくれる。
  6. 削除

    • $ gem yank <gemname> -v x.x.x で RubyGems から削除できる。
    • 履歴は残るので、完全に消すのは中の人にコンタクトするしかないよう。

C拡張を含むgemを公開する場合

C拡張を含むgemを作りたい場合は、ちょっと面倒です。

日本語の記事が見つからなかったので、Gems with Extensionsを参考にして頑張りました。

ここでは、immutable_list という gem 名を例にとって、C拡張を含むgemを公開するまでを説明します。

1. 雛形を作る

$ bundle gem immutable_list
      create  immutable_list/Gemfile
      create  immutable_list/Rakefile
      create  immutable_list/LICENSE.txt
      create  immutable_list/README.md
      create  immutable_list/.gitignore
      create  immutable_list/immutable_list.gemspec
      create  immutable_list/lib/immutable_list.rb
      create  immutable_list/lib/immutable_list/version.rb
Initializating git repo in /Users/gam0022/git/gem/t/immutable_list

2. 雛形を編集

C拡張を自動でコンパイルするようにするために、書き加えないといけないことが多いです。

Gemfile

 source 'https://rubygems.org'

 # Specify your gem's dependencies in immutable_list.gemspec
 gemspec
+gem "rake-compiler"

Rakefile

 require "bundler/gem_tasks"
+require "rake/extensiontask"
+
+Rake::ExtensionTask.new "immutable_list" do |ext|
+  ext.lib_dir = "lib/immutable_list"
+end

immutable_list.gemspec

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

 Gem::Specification.new do |spec|
   spec.name          = "immutable_list"
   spec.version       = ImmutableList::VERSION
   spec.authors       = ["gam0022"]
   spec.email         = ["gam0022@gmail.com"]
-  spec.description   = %q{TODO: Write a gem description}
-  spec.summary       = %q{TODO: Write a gem summary}
+  spec.description   = %q{Immutable Linked List implemented in C-Extensions}
+  spec.summary       = %q{Immutable Linked List implemented in C-Extensions}
   spec.homepage      = ""
   spec.license       = "MIT"
+  spec.extensions    = %w[ext/immutable_list/extconf.rb]

   spec.files         = `git ls-files`.split($/)
   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.3"
   spec.add_development_dependency "rake"
 end

ext/

C拡張のライブラリ本体は、ext/に置きます。

ext/ は無いので、新規で作ります。

$ tree ext/
ext
└── immutable_list
    ├── extconf.rb
    └── immutable_list.c

immutable_list/immutable_listのようなファイル名の指定がテクニックなので注目してください。 <gemname>/<gemname>にすることで、require をするときに名前が衝突することを防いでいます。

ext/immutable_list/extconf.rb

require "mkmf"
create_makefile("immutable_list/immutable_list")

ext/immutable_list/immutable_list.c

#include <stdio.h>
#include <ruby.h>

#define true 1
#define false 0

VALUE cImmutableList;

struct immutable_list {
  VALUE value;
  VALUE next;
};

static void
immutable_list_mark(struct immutable_list *ptr)
{
  rb_gc_mark(ptr->value);
  rb_gc_mark(ptr->next);
}

// 長すぎるので以下略。気になる人は以下を参照
// https://github.com/gam0022/immutable_list/blob/master/ext/immutable_list/immutable_list.c

lib/

通常のgemであれば、lib/immutable_list.rb に本体を実装しますが、 今回はC拡張で実装されたもの require するようにします。

$ tree lib/
lib
├── immutable_list
│   └── version.rb
└── immutable_list.rb

lib/immutable_list.rb

 require "immutable_list/version"
+require "immutable_list/immutable_list"

-module ImmutableList
+class ImmutableList
   # Your code goes here...
 end

lib/immutable_list/version.rb

-module ImmutableList
+class ImmutableList
  VERSION = "0.0.1"
end

RubyGems に登録

あとは、通常のgemと同じ要領で、RubyGems に公開するだけです。

  • $ gem build immutable_list.gemspec
  • GitHub に push
  • $ gem push pkg/immutable_list-0.0.1.gem
comments powered by Disqus

gam0022.net's Tag Cloud