5/02/2007

require 'rubygems'

寫到一半因為有其他事所以先放著,結果回來就是八小時後了…。
有夠累。

==

大致調查(survey?)了一下要怎麼樣把程式 package 成 rubygem, 主要參考書籍當然是 Programming Ruby 2nd, 這本我看好久還是沒看完的書。(還不都是因為他太厚了…)在 Creating Your Own Gems 這一節中,相當詳細地描述了如何打包(在 MtG 中,不知為何把 entwine 翻譯成打包,瞬間從 spell 的意味變成像外帶一樣)。這邊將簡單示範一次我如何發佈 ludy 的。

首先呢,rubygems 有建議的檔案配置(layout),可以不依照這個規則來做,但一般來說建議使用跟別人相同的方式。所有雜七雜八的東西放在根目錄下,例如 ludy-0.0.1 裡面放的有:ludy.gemspec, LICENSE, NOTICE, README. 其他目錄則是很常見的:bin, doc, lib, test. 我沒有 bin 也沒有 doc, 所以只有放 lib 和 test 兩個目錄。

顧名思義,lib 裡面放的就是 ludy 本身的 source code, 專門拿來給人 require 用。而 test 底下則是所有的 test program, 以 tc_ 開頭的是 TestCase, 可以單獨執行,但主要是由 ts_ 開頭的 TestSuite 去 require 起來,然後 ts_ 會自動執行這些 tc_.

所以我的 ts_ludy.rb 裡面只有兩行:

require(File.join(File.dirname(__FILE__), '..', 'lib', 'ludy'))
require_all_in_dir __FILE__

第一行是把 lib/ludy.rb require 進來,這是使用 ludy 部份元件的必要手續。這樣做的理由是有些 path 問題很煩,所以我把處理 path 的 func 寫在 ludy.rb 中,如此一來需要確保路徑的只有 ludy.rb 這一個檔案。至於為何這裡要使用相對路徑去 require 呢?因為我堅持兩種使用 ludy 的方式,一個是安裝成 gem, 另一個是直接丟到 project 目錄下,使用路徑去 require. 前者不用說,可以有一個很標準的方式使用,但後者就不一定了。

為了處理這個問題,ludy 內部需要某些其他 ludy tool 時,就改用 require_ludy, 集中處理 require 問題,避免重複把相同路徑加入 load path. 使用者當然也可以使用 require_ludy, 或是依然習慣使用 require, 那麼只要確定自己 require 的 path 是正確的就好了,ludy 內部不會有路徑問題。至於 gem version, require 的方式就很單純用 require 'ludy/tool_name' 或是 require_ludy 'tool_name' 就好了。

第二行,則是把所有 tc_ 開頭的 TestCase require 進 TestSuite 裡面,(其實是同個目錄下所有的 .rb 檔,只是現在 test/ 下只有 tc_ 和正在執行的 ts_ 而已)unit test 就會自動執行了。不過這個 require_all_in_dir 的第一個參數卻是檔案名稱,名字好像取得不太好的樣子。

ok, 也就是說我要打包的東西是 ludy.gemspec, LICENSE, NOTICE, README, lib/*.rb, test/*.rb. 其中 lib/ 底下還有其他資料夾,也要一併打包。那麼寫好的 gemspec 就是


require 'rubygems'

spec = Gem::Specification.new{|s|
s.name = 'ludy'
s.version = '0.0.1'
s.author = 'Lin Jen-Shin(a.k.a. godfat)'
s.email = 'strip number: 135godfat7911@246gmail.890com'
s.homepage = 'http://ludy.rubyforge.org/'
s.platform = Gem::Platform::RUBY
s.summary = 'Aims to extend Ruby standard library, providing some useful
tools that\'s not existed in the standard library.'
candidates = Dir.glob '{bin,doc,lib,test}/**/*'
candidates+= Dir.glob '*'
s.files = candidates.delete_if{|item|
item.include?('CVS') || item.include?('rdoc') ||
File.extname(item) == '.gem'
}

s.require_path = 'lib'
s.autorequire = 'ludy'
s.test_file = 'test/ts_ludy.rb'
s.has_rdoc = false
# s.extra_rdoc_files = []
# s.add_dependency 'multi', '>=0.1'
}

if $0 == __FILE__
Gem::manage_gems
Gem::Builder.new(spec).build
end


其中 Dir.glob 的部份我還沒研究,所以寫得很爛,居然用補正的方式。反正就是全部抄自 Programming Ruby 2nd, 再做一點修修改改。其中最下面的 if $0 == __FILE__ 可以判斷這個程式是不是直接被 ruby interpreter 執行?如果是的話,就呼叫建立 rubygem 的 method. 不是的話,就只要定義 spec 就好了。

前者就是:
ruby ludy.gemspec
或是如果有加 #! 的話:
./ludy.gemspec
如果 ludy.gemspec 在現在目錄下的話。

後者就是:
gem build ludy.gemspec

這兩種方式都可以產生 ludy-0.0.1.gem. 接著再下:
gem install ludy
就可以正確安裝 ludy gem 了。至於 remote 的部份呢?只要上傳到 rubyforge 的 file release system 上,rubyforge 每日會有數次掃描裡面的 *.gem, 自動放到 rubygems 的 repository. 這件事我已經做完了,所以現在可以直接:

gem install ludy

就能看到我寫的一點程式了 :)
不過其實我只是在測試功能而已,本來是想標版本號 0.0.0, 可惜這樣用:

gem uninstall ludy

時會出一點問題。所以就還是標成 0.0.1, 但未來的更新暫時都不改版本號。正式釋出時大概會是 0.0.2 吧 :p 還是歡迎大家抓回去試試,使用方法全在 unit test 裡面,文件懶得寫了。

homepage:
http://ludy.rubyforge.org/
有空時會弄個應有的版面出來。

2007.05.02 godfat 真常

沒有留言: