有關 Bundler 的二三事

今天聽 Ruby Rouges podcast #045, 學了一些之前不懂的 bundler 小知識.

Dependency management 問題是 NP-Complete

Bundler,或任何 dependency management 軟件的算法,也是 NP-Complete 的問題

Bundler 的工作其實是在所有 dependencies 的版本上搜尋能符合 Gemfile 內容的解。大部份情況 bundler 也可以很快找到答案,但有一些 Gemfile 會讓 bundler 停轉。雖然看上去像 bug,但實際上不是個 bundler 作者可以輕易解決的問題。

話雖如此,但其實有方法加快 bundler 的搜尋:在 Gemfile 的 dependencies 盡量指定你想要的版本。

例如這個 Gemfile 的 Gems 沒有指定版本:

gem 'rake'
gem 'rack'
gem 'jekyll'

Bundler 預設值下,gems 版本等於 '>= 0' (每一個版本也可以)。假設每個 gems 有 10 個版本,(簡單起見不計他們的 dependencies)這個 Gemfile 的版本的 search space 便有 10^3 個。

但如果將之指定版本 :

 gem 'rake', '~> 1.3.0'
 gem 'rack', '>= 0.9.2'
 gem 'jekyll', '>= 0.11.2'

那他們的 search space 便會降到只有附合條件(數個到數十個)的版本了!

一般會讓 bundler 停轉的 Gemfile 在指定較少範圍的 gems version 後都能解決問題 。

semantic versioning 和 approximate operator

如果你在用 Bundler 那你大概己聽過 semantic versioning。上面 Gemfile 例子裡的 approximate operator (~>) 意思為 "只要版本大過或等於指定的版本,最後一個版本號碼可以不理"。例如 '~> 3.1.2' 即 '>= 3.1.2 and < 3.2.0','~> 3.1' 等於 '>= 3.1 and < 4.0'。這是基於 semantic versioning 的假設:如果在版本 a.b.c 測試過,,那麼在之後的 a.b.d 也應該可以運作正常 -- 因為最後一個數字僅代表 patch / bug fix 版本。

值得留意的是,~> 這符號之前沒有名字。Rubygems 的 committer 有段時期將之稱為 "spermy operator"!這事還引發了一串討論,最後 ~> 官方名稱就確定爲 "approximate"。

Bundler 1.1.0 的改進

如果你有用過 bundler 也會覺得它初次 bundle install 時很慢。也許大家會覺得那是因為 dependency resolution 的問題。然而大多數時間這也不是 NP-Complete 問題的問題. bundler 一直慢的原因是因為在解決版本問題之前它先會由 rubygems 下載整個 rubygems 資料庫 -- 由古到今所有的 gems 的不同版本 -- 將之解壓和放到計憶體上,這不是個很快的過程。

在 Bundler 1.1.0,他們使用了 rubygems.org 的新功能:只返回需要 gems 的資料。這讓需要下載和處理的資料由 M 降到 K 為單位!如果你在用 Bundler,那你一定要看看你更新到最新版本了沒!

推介你聽聽 Ruby Rouges podcast #045 裡面有我未能盡錄的好內容啊!