株式会社クリアコード > ククログ

ククログ


GaaS: Groonga as a Service

4年ぶりにインターンシップを開始することを2月にお知らせしました。そこで予告していた通り、4月からインターンシップを実施します。インターンシップをどのように実施するかを3行で説明すると以下の通りです。

  • 目的: インターンがクリアコードで実践している開発方法を体験する
  • 内容: クリアコードのメンバーと一緒にフリーソフトウェアを開発する
  • 対象: プログラミングが好きでフリーソフトウェアに関心がある16歳以上の方(学生、社会人、休職中などは問わない)

詳細はインターンシップページを確認してください。

現時点で紹介している一緒に開発するフリーソフトウェアは以下の通りです。

1つめはJavaScriptやFirefoxに関連した開発になります。2つめはC、Objective-C、GTK+に関連した開発になります。

今回紹介するフリーソフトウェアはRubyやgroongaに関連した開発になります。

これまで紹介したフリーソフトウェアも含めて、ぜひクリアコードの開発方法を体験しながら開発したいと感じた方はインターンシップページにある方法で応募してください。

GaaS: Groonga as a Service

自分でセットアップしなくてもgroongaを使えたらステキではありませんか?groongaをサービスとして提供する、Groonga as a Service。略してGaaS(ガース)。そんなサービスを実現するためのフリーソフトウェアをインターンシップで開発します*1

GaaSを使えば、Herokuアドオンとしてgroongaを提供することもできます。夢が広がりますね*2

GaaSの構想は数年前からありましたが、groonga本体の開発などに注力しており未着手です。しかし、GitHub上にリポジトリを作成済みだったり、どんな構成にするのがよいかをスケッチしたりと実現に向けた準備は進めています。

GaaSの機能

GaaSは以下の機能を提供します。

  • APIでgroongaサーバーを追加・削除する機能
    • 負荷の増大・縮小に動的に対応できる
    • 追加時は既存のgroongaサーバーと同じデータを持つ
  • 同じデータに対して、複数のgroongaサーバーで独立して検索サービスを提供する機能
    • 負荷の増大に対応できる
  • 複数のgroongaサーバーがいるときの更新処理はすべてのgroongaサーバーで実行する機能
    • レプリケーション機能
  • 認証機能
  • groongaサーバー毎に使用可能なリソース量を制限する機能
  • groongaサーバーで更新されたデータをリアルタイムでバックアップするAPI

一方、以下の機能は提供しません。

  • 同じデータを複数のgroongaサーバーに分散して配置し、分散して1つのクエリを実行する機能
    • シャーディングはできない
    • 分散して検索した結果をマージできない
  • データの永続性を保証する機能
    • groongaサーバーを落としたらそのgroongaサーバーが持っていたデータは消える

このことからGaaSは以下のようなケースで有用です。

  • るりまサーチのようにドキュメントを全文検索するケース
    • マスターデータが別にあるのでデータの永続性がなくても困らない(作りなおせばよい)
    • 検索数が増えたらgroongaサーバーを追加すれば対応できる
  • ブログの全文検索機能
    • マスターデータが別にあるのでデータの永続性がなくても困らない(作りなおせばよい)
    • 検索数が増えたらgroongaサーバーを追加すれば対応できる
  • 中規模(数十万レコード)くらいまでのECサイト
    • groongaサーバーを追加して負荷を分散できる
    • groongaはすぐに更新結果を検索に反映できるので、品切れになった商品はすぐにヒットしなくなる
    • バックアップAPIで更新データのバックアップをとっておけば、groongaサーバーがすべて落ちても復旧可能
    • 複数のgroongaサーバーの管理を任せられる

シャーディング機能がないためレコード数(データ量)が多いケースには対応できませんが、レコード数がそれほど多くない場合は運用が楽になるため有用です。

GaaSの実現方法案

GaaSの以下のそれぞれの機能の実現方法案を説明します。まだ作っていないので、実際に作ったらここから大きく方向が変わる可能性があります。

  • APIでgroongaサーバーを追加・削除する機能
  • 同じデータに対して、複数のgroongaサーバーで独立して検索サービスを提供する機能
  • 複数のgroongaサーバーがいるときの更新処理はすべてのgroongaサーバーで実行する機能
  • 認証機能
  • groongaサーバー毎に使用可能なリソース量を制限する機能
  • groongaサーバーで更新されたデータをリアルタイムでバックアップするAPI
APIでgroongaサーバーを追加・削除する機能

まずは、APIでgroongaサーバーを追加・削除する機能の実現方法案を説明します。

簡単のために、groongaサーバーを追加したい時、空いているサーバーはすでにあるとします*3。問題は初期データをどうするかです。初期データは指定したURLからHTTPでダウンロードすることにします。こうすることで、GaaS側では初期データを管理する必要がなくなります。また、追加するサーバーは常に最新の初期データを参照できるので、どのgroongaサーバーの初期化処理でも手順が同様になりgroongaサーバーの追加が簡単です。

起動時の流れは以下のようになります。

  1. 初期データをHTTPでダウンロード
  2. 初期データをgroongaに投入
  3. groongaサーバー起動
  4. サービス開始

groongaサーバーの削除は、groongaサーバーを終了して関連ファイルを削除するだけです。

APIは作ればよいだけなので省略します。

同じデータに対して、複数のgroongaサーバーで独立して検索サービスを提供する機能

検索サービスを複数のgroongaサーバーで分担して提供する機能の実現方法案を説明します。

groongaはHTTPサーバーとして検索APIを提供できます。そのため、すでにあるHTTP関連の技術を使うことができます。クライアントからのリクエストを複数のgroongaサーバーに振り分ける機能はリバースプロキシを使って実現できるでしょう。

複数のgroongaサーバーがいるときの更新処理はすべてのgroongaサーバーで実行する機能

レプリケーション機能の実現方法案について説明します。

fluent-plugin-groongaを使うことにより、1つのgroongaサーバーで実行した更新処理を他のサーバーでも実行することができます。これによりレプリケーション機能を実現できます。

残る問題は、クライアントからのリクエストが更新リクエストか検索リクエストかを判断する方法です。更新リクエストの場合はfluent-plugin-groongaで処理しなければいけませんが、検索リクエストの場合は別々のgroongaサーバーで処理したいです。これは、以下のような構成にすることで実現可能ではないかと考えています*4

まず検索時の構成案を示します。

検索時
                                    +--> fluentd -----> groonga
クライアント --> リバースプロキシ --+--> fluentd -----> groonga
                                    +--> fluentd -----> groonga

検索時は、fluentdは単にデータを中継します。

更新時は、fluentdが更新クエリを全てのgroongaに配布します。以下は、真ん中のfluentdに更新リクエストが飛んだ場合です。同様に、他のfluentdに更新リクエストが飛んだ場合もすべてのgroongaサーバーに変更が反映されます。

更新時
                                         fluentd   +--> groonga
クライアント --> リバースプロキシ -----> fluentd --+--> groonga
                                         fluentd   +--> groonga
認証機能

1つのGaaSシステムで複数のユーザーをホストするためには、他のユーザーのデータが読めないように認証機能が必要です。認証機能はHTTPの仕組みを使えばどうとでもなりそうです。

groongaサーバー毎に使用可能なリソース量を制限する機能

1つのGaaSシステムで複数のユーザーをホストした場合、1人のユーザーがリソースを使いすぎて他のユーザー用のサービスが提供できなくなることは問題です。そのため、groongaサーバー毎に使用可能なリソース量を制限し、制限を超えた場合は自動で再初期化したり、サービスを停止したりするような仕組みが必要です。GodBluepillなどリソース監視も備えたプロセス監視システムはいくつかあるので、それらを使用すれば実現できるでしょう。あるいは、単に、ulimitとプロセス監視システムの組合せでもよいかもしれません。

groongaサーバーで更新されたデータをリアルタイムでバックアップするAPI

groongaサーバーで更新されたデータをリアルタイムでバックアップするAPIがあれば、初期データを随時更新して、groongaサーバーを追加した時に使う初期データを最新のデータにしておくことができます。

GaaSのレプリケーション機能はfluent-plugin-groongaで実現するので、fluentdのcopy outputプラグインとforward outputプラグイン機能を使ってこの機能を実現できます。

まとめ

groongaをサービスとして提供する仕組みGaaS(Groonga as a Service。ガース。)の構想とそれの実現方法案を紹介しました。

GaaSを一緒に開発しながらクリアコードの開発方法を体験したいという方はインターンシップページを確認の上、応募してください。

他のインターンシップ対象のフリーソフトウェアを再掲します。

*1 ここでは「GaaS」という単語をgroongaの機能を提供するサービスおよびそのサービスを実現するソフトウェアの両方を示すものとして使っています。サービスだけを提供するのではなく、サービスを提供するソフトウェアも提供することで、自分の環境にサービスを構築することもできるようにします。データを外部に出したくない場合でも使えるようになります。

*2 groongaをHerokuアドオンとして提供することに(インターンシップとは関係なく)興味のある方はお問い合わせフォームからご連絡ください。一緒に実現させましょう。

*3 Amazon EC2などを使えば追加したい時に動的にサーバーを追加することはできるので、このあたりはそんなに困らないはず。groongaサーバーを設定するためのChefのcookbookを作っていたりします。

*4 要検討

2013-04-01


Rubyで定義したメソッドの引数にYARD用のドキュメントを書く方法

はじめに

YARDというRuby用のドキュメンテーションツールがあります。以前、Cで記述したRubyの拡張ライブラリにYARD用のドキュメントを書く方法を説明しました。今回は、Rubyで定義したメソッドの引数についてYARD用のドキュメントを書く方法を説明します。

引数を対象とする理由は、引数のドキュメントを書く機会が多いからです。ライブラリのドキュメントは、メソッドについて書くことがほとんどです。メソッドには引数があることが多いため、引数の説明を書く機会が多いのです。

この記事では、まず、YARDで使う記法について説明します。その後、引数についてのドキュメントを書くための@paramタグについて説明し、実際にRubyのコードにドキュメントを書いていきます。ドキュメントを書くごとにHTMLを生成して、@paramタグで書いたドキュメントがどのように表示されるかを確認します。

YARDのタグとディレクティブ

まず、YARDでドキュメントを書くときに使う記法を説明します。

YARDには「タグ」と「ディレクティブ」という2つの記法があります。このタグとディレクティブを使って、コメントとしてドキュメントを書きます*1。タグとディレクティブは、それぞれ次のように使います。

記法使い方
タグコードに関するメタデータを埋め込む
ディレクティブドキュメントの内容そのものを操作する

タグとディレクティブの詳細は公式のドキュメント(英語)に詳しく書かれているので、興味のある方はこちらをご覧ください。引数についてドキュメントを書くには、たくさんあるタグのうち@paramタグを利用します。それでは、この@paramタグについて説明します。

@paramタグ

@paramタグは「引数」というメタデータを埋め込むためのタグです。引数がどんなクラスのオブジェクトであることを期待しているか(以降、「引数のクラス」と呼ぶ)や、引数の説明を文章で書くために使います。1つの引数につき1つの@paramタグを書きます。引数が複数ある場合は複数の@paramタグを書きます。

@paramタグの書式は以下の通りです。

@param 引数名 [引数のクラス] 説明

どこにどのように書けばよいかなど、実際の使い方は後ほど説明します。その前に、@paramタグの使い方を示すために使うサンプルコードを示します。このコードに対して@paramタグでドキュメントを書いていきます。

サンプルコード

以下にRubyのコードを示します。このコードに対してドキュメントを書いていきます。

このコードはextract_overageメソッドを定義しています。extract_overageメソッドは、配列と数値を引数にとり、各要素から数値より大きい数(数値が指定されない場合は20より大きい数)を集めた配列を返します。例えば、extract_overage([17, 43, 40, 56], 40)を実行すると、[43, 56]を返します。

1
2
3
4
5
6
7
# Returns Array containing numbers exceeding limit_age.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
def extract_overage(ages, limit_age=20)
  ages.select do |number|
    number > limit_age
  end
end

このコードを、以降、「サンプルコード」と呼びます。

サンプルコードで定義しているextract_overageメソッドのコメントに、メソッドの説明を書いておきました*2。ただし、引数についての説明は書いていません。引数についての説明はこのあと@paramタグで書いていきます。

なお、YARDはメソッド定義の直前のコメントに書いている内容をメソッド全体の説明として扱います。それを確かめるために、サンプルコードからHTMLのリファレンスマニュアルを生成してみましょう。サンプルコードをexample.rbというファイルに保存して、次のコマンドを実行することで、HTMLのリファレンスマニュアルを生成できます。

$ yardoc example.rb

生成されたリファレンスマニュアルは以下のようになります。

メソッド全体の説明が表示されたリファレンスマニュアル

図の赤枠の部分を見ると、コメントに書いた説明がメソッド全体の説明としてextract_overageメソッドのところに表示されています。また、緑色の枠の部分には、メソッド全体の説明の1行目がサマリーとして表示されています。

@paramタグの使い方

それでは、@paramタグで引数のドキュメントを書きましょう。ここからは、サンプルコード内のextract_overageメソッドの中身を省略します。これは、ドキュメントを書く場所をわかりやすくするためです。メソッドの中身を省略すると以下のようになります。

1
2
3
4
# Returns Array containing number exceeding limit_age.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
def extract_overage(ages, limit_age=20)
end
1つ目の引数のドキュメントを書く

ではまず、1つ目の引数であるagesについてのドキュメントを書きます。@paramタグの書式を再掲します。

@param 引数名 [引数のクラス] 説明

1つ目の引数agesについて記述するので、引数名はages、 引数のクラスはArrayとなります。説明は「numbers checked if they exceeded from limit_age.」となります。

次に、@paramタグを書く場所を説明します。@paramタグは、メソッド全体の説明と同じく、メソッド定義の直前のコメントに書きます。メソッド全体の説明の下に空のコメント行を1行入れ、その下に書くとよいでしょう。このように書くと、コードに書かれたドキュメントを読むときに理解しやすくなります*3。この順序でドキュメントを書くと、ドキュメントを読む人はメソッド全体の説明を読んでから個々の引数の説明を読めるからです。全体を把握してから細部を見る、という順序で読むことができ、メソッドを理解しやすくなります。

それでは、@paramタグで引数agesの説明を書きます。

1
2
3
4
5
6
# Returns Array containing numbers exceeding limit_age.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
#
# @param ages [Array] numbers checked if they exceeded from limit_age.
def extract_overage(ages, limit_age=20)
end

上記のコードからHTMLのリファレンスを生成すると次のようになります。図の赤枠のところが今回追加された説明です。

1つ目の引数の説明が表示されたリファレンスマニュアル

引数agesについての説明が追加されています。

2つ目の引数のドキュメントを書く

次に2つ目の引数のlimit_ageについても同じように書いていきましょう。

1つの@paramタグで1つの引数の説明を書きます。引数が複数ある場合はその数だけ@paramタグを使います。コメントに書かれたドキュメントを読むときには、似たような情報がまとまっていた方が読みやすいので、引数のドキュメントは並べて書くのがよいでしょう。

1つめの引数から順番にドキュメントを書くとメソッド定義と対応がとりやすくなるので理解しやすくなります。これはコードに書かれたドキュメントを読むときもHTMLのリファレンスマニュアルを詠むときも同様です。HTMLのリファレンスマニュアルでは、同じタグ(ここでは@paramタグ)は書かれた順番に表示されるからです。

では、引数limit_ageについてのドキュメントを書きます。

1
2
3
4
5
6
7
8
# Returns Array containing numbers exceeding limit_age.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
#
# @param ages [Array] numbers checked if they exceeded from limit_age.
# @param limit_age [Integer] limit_age limit used to extract
#   bigger ages than it.
def extract_overage(ages, limit_age=20)
end

追加した@paramタグでは、説明の文章を折り返しています。# bigger ...の行です。@paramタグで書くドキュメントを途中で折り返すときは、@paramタグの開始行よりもインデントしてください。インデントすると、YARDはインデントされた行を前の行の継続行として扱います。

それでは、上記のコードからHTMLのリファレンスを生成しましょう。次のようになります。図の赤枠のところが今回追加した説明です。

2つ目の引数の説明が追加されたリファレンスマニュアル

limit_ageについての説明が追加されています。折り返された説明は1つの文として扱われています。

extract_overageメソッドを呼び出すときに引数limit_ageを省略すると自動的に20がデフォルト値として使用されますが、HTMLのリファレンスマニュアルにはそのことが(default to: 20)として表示されています。これは、YARDがメソッド定義から判断して表示しています。そのため、明示的にドキュメントに書かなくてもデフォルト値が表示されます。

ここでは以下のことを説明しました。

  • 説明が長くなるときは折り返してインデントをすること
  • デフォルト値はYARDが自動的に判断してくれること

まとめ

今回は、Rubyで定義したメソッドの引数にYARD用のドキュメントを書く方法を説明しました。ドキュメントを書くことで、ユーザーがソフトウェアを使いやすくなります。それだけではなく、開発者としてもよいソフトウェアを書くのに役立つのでドキュメントを書いてみてはいかがでしょうか。

次回は、@returnタグを使って戻り値についてドキュメントを書く方法を説明する予定です。

*1 これはCで記述したRubyのライブラリにYARD用のドキュメントを書く場合と同じです。

*2 ここではあえてタグを使っていません。理由は@paramタグに注目してもらうためです。

*3 HTMLのリファレンスマニュアルを生成するときには関係ありません。

タグ: Ruby
2013-04-03

Fedoraプロジェクトで新規パッケージをリリースする方法

はじめに

今回は、Fedoraプロジェクトで新規パッケージをリリースする方法を、Cutterリリースしたときの経験をもとに紹介します。

Cutterプロジェクトでは、従来はFedora向けに独自にリポジトリを用意してRPMを提供していました。しかし、この方法では、ユーザーがCutterをインストールするときにリポジトリを追加登録する手間がかかり、不便でした。 そこで、FedoraでCutterを利用したいユーザーがもっと簡単に導入できるように、FedoraプロジェクトからRPMを提供するようにしました*1

新規パッケージリリースまでの流れ

新規パッケージの公開までの流れについては、Join the package collection maintainersに詳細がまとめられています。

このドキュメントは日本語訳もあります。 ところどころ内容が最新版に追従していませんが、参考としては十分です。

まずはじっくり上記ドキュメントを読まれることをお勧めしますが、簡単に紹介すると以下のようになります。

  • Bugzillaアカウントの取得
  • Fedora Account System(FAS)のアカウントの取得
  • メーリングリストへの参加
  • レビューリクエストの準備
  • レビューリクエストの投稿
  • レビューリクエストの承認
  • SCMリクエスト
  • パッケージのインポート
  • パッケージのコミット
  • パッケージのビルド
  • パッケージのアップデート
  • パッケージのテスト
  • パッケージのリリース

それでは、順を追って説明していきます。

Bugzillaアカウントの取得

まずはRed Hat Bugzillaのアカウントを取得します。

BugzillaのアカウントはNew Accountのリンクから作成します。

このアカウントは後述するレビューリクエストを行うときに必要になります。

Fedora Account System(FAS)のアカウントの取得

Fedoraプロジェクトでパッケージをメンテナンスするためには、FASアカウントと呼ばれるメンテナンスのためのアカウントが必要です。

FASアカウントはFedora Account SystemにアクセスしNew Accountのリンクから作成します。

このアカウントはレビューリクエストを行う際や、レビューリクエストが承認されたあとのパッケージのインポートやコミット等のリポジトリ関連の作業に使います。

Contributors License Agreement(CLA)への同意

FASアカウントを登録したら、Contributors License Agreement(貢献者ライセンス同意書、以下CLA)に同意する必要があります。

同じくFASアカウントでログインした状態から complete the CLA のリンクをたどり、ライセンスを読んだうえで、同意します。

詳しくは1.5. CLA に署名するを参照してください。

FASアカウントでCLAに同意した状態のスクリーンショット

CLAに同意すると、図の状態になります。

公開鍵の登録

FASアカウントを登録したら、ログイン後、"My Account"メニューよりSSHの公開鍵を登録します。

ここで登録した鍵を用いて後述するfedorapeople.orgやリポジトリへとアクセスします。

後々の便利のために、~/.ssh/configに以下の設定を追加しておくと良いでしょう(IdentityFile ~/.ssh/id_rsa.fedora.pkeyの設定は個々の環境で読み替えてください)。

HOST *.fedoraproject.org fedorapeople.org *.fedorahosted.org
     IdentityFile ~/.ssh/id_rsa.fedora.pkey
メーリングリストへの参加

これは必須ではありませんが、他のパッケージのレビューを見ることで、パッケージング作業についてより深く学べます。

他にもいくつか参考になるメーリングリストが存在します。 必要に応じて参加すると良いでしょう。

メーリングリスト説明
devel-announceアナウンス向けのメーリングリスト。流量が少ない。最近だとFedora 19のAlphaフリーズのアナウンスが流れた。
devel開発者向けのメーリングリスト。流量が多い。
package-announceパッケージのリリースアナウンス向けのメーリングリスト。パッケージがstable入りしたのを知るために登録する。
レビューリクエストの準備

新規パッケージを登録するためには、レビューというプロセスを経る必要があります。 レビューをしてもらうには以下の情報が必要です。

  • specファイルのURL
  • SRPMファイルのURL
  • FASアカウント名

specファイルやSRPMファイルの場所は一般にアクセス可能な場所であれば、どこでも構いません。

Cutterの場合はSourceforge.netを利用していたので、一時ディレクトリを用意して、そこにファイルをアップロードしていました。

レビューリクエストを行うまえに、あらかじめパッケージに問題がないか確認する方法をいくつか紹介します。

  • rpmlintによるチェック
  • Kojiによるビルドチェック
  • mockによるビルドチェック
  • fedora-reviewによるチェック
rpmlintによるチェック

パッケージに問題がないか確認する最もシンプルな方法です。

$ rpmlint -i *.rpm

パッケージのレビューに関してはReviewGuidelinesというドキュメントがあります。 このドキュメントには、レビュワーがしなければいけないことの1つとして「rpmlintの実行」が記載されています。

あらかじめrpmlintで報告されている警告やエラーを潰しておくと、レビューしてもらうときの余計なやりとりを減らすことができます*2

Kojiによるビルドチェック

Fedoraプロジェクト向けにRPMパッケージをビルドするシステムとしてKojiがあります。

Kojiを使うと複数のリリースもしくは複数のアーキテクチャ向けにRPMパッケージのビルドがうまくいくか確認することができます*3

Kojiを使うにはいくつかセットアップが必要です。

Kojiについての詳細はUsing the Koji build systemを参照してください。

まずはfedora-packagerをインストールします。

$ sudo yum install fedora-packager

次にfedora-packager-setupを実行します。このとき、FASアカウントの入力が必要です。 また、ブラウザ向けのクライアント証明書のパスワードの入力も必要です。

$ fedora-packager-setup

Setting up Fedora packager environment
You need a client certificate from the Fedora Account System, lets get one now
FAS Username: kenhys
FAS Password: 
Saved: /home/kenhys/.fedora-server-ca.cert
Linking: ~/.fedora-server-ca.cert to ~/.fedora-upload-ca.cert
Setting up Browser Certificates
Enter Export Password:
Verifying - Enter Export Password:

Browser certificate exported to ~/fedora-browser-cert.p12
To import the certificate into Firefox:

Edit -> Preferences -> Advanced
Click "View Certificates" 
On "Your Certificates" tab, click "Import" 
Select ~/fedora-browser-cert.p12
Type the export passphrase you chose earlier

Once imported, you should see a certificate named "Fedora Project".
Your username should appear underneath this.

You should now be able to click the "login" link at http://koji.fedoraproject.org/koji/ successfully.

importing the certificate is optional and not needed for daily use.
you should also import the ca cert at ~/.fedora-upload-ca.cert

パスワードを入力し終わると処理が進みます。

fedora-packager-setupコマンドは以下の4つのファイルをホームディレクトリへと保存します*4

  • .fedora-server-ca.cert
  • .fedora-upload-ca.cert(.fedora-server-ca.certへのシンボリックリンク)
  • .fedora.cert
  • fedora-browser-cert.p12

この手順は通常一度だけ必要です*5

これで、Kojiを使う準備が整いました。

Kojiを使ってビルドを試すには、以下の様に--scratchオプションを与えてkojiコマンドを実行します。

$ koji build --scratch f18 cutter-1.2.2-4.fc18.src.rpm

KojiでのビルドはすべてFedoraプロジェクトのビルドサーバーで行うので、他のビルドとの兼ね合いからビルドが完了するまでそれなりに時間がかかります。

Mockによるビルドチェック

依存関係などが適切に処理されているかについてはMockによるビルドを行うことで確認できます。

KojiもMockを使って実現しています。

Mockではローカルにchroot環境を構築してRPMをビルドするようになっています。

常用しているFedoraの環境とは異なりクリーンな環境でビルドを行うため、依存関係の抜け等をチェックすることができます*6

Mockを使うにはいくつかセットアップが必要です。

まずはmockグループに作業ユーザ(任意)を追加します。この作業は初回のみ必要です。

$ sudo usermod -a -G mock (作業ユーザーアカウント)

次にmock環境を初期化します。この作業はビルドを試したい環境ごとに行う必要があります。

$ mock -r fedora-18-x86_64 --init

最後にmockの特定環境でビルドするには以下のコマンドを実行します。

$ mock -r fedora-18-x86_64 --no-clean cutter-1.2.2-4.fc18.src.rpm

mockコマンドはビルド結果のログを以下に生成します。また、ビルドしたRPMも同じ階層に保存します。

/var/lib/mock/fedora-18-x86_64/result/build.log

Mockの詳細についてはUsing Mock to test package buildsを参照してください。

fedora-reviewによるチェック

fedora-reviewという専用のツールを使って確認することもできます。 以下のようにしてインストールします。

$ sudo yum install fedora-review

fedora-reviewはレビュワー向けのツールなので、パッケージに関するより細かいレポート結果を得ることができます。 ローカル環境で実行するには以下のようにSRPMを指定します。

$ fedora-review --rpm-spec --name cutter-1.2.2-4.fc18.src.rpm

実行が終了すると、fedora-reviewコマンドは(パッケージ名)/review.txtを生成します。 review.txtの内容は以下のように、チェックする項目とその結果を含んでいます。

Package Review
==============

Key:
[x] = Pass
[!] = Fail
[-] = Not applicable
[?] = Not evaluated
[ ] = Manual review needed

Issues:
=======
- Package do not use a name that already exist
  Note: A package already exist with this name, please check
  https://admin.fedoraproject.org/pkgdb/acls/name/cutter
  See: https://fedoraproject.org/wiki/Packaging/NamingGuidelines#Conflicting_Package_Names

===== MUST items =====

C/C++:
[ ]: Package does not contain kernel modules.
[ ]: Package contains no static executables.
[ ]: Development (unversioned) .so files in -devel subpackage, if present.
     Note: Unversioned so-files in private %_libdir subdirectory (see
     attachment). Verify they are not in ld path.
[x]: Header files in -devel subpackage, if present.
[x]: ldconfig called in %post and %postun if required.
[x]: Package does not contain any libtool archives (.la)
[x]: Rpath absent or only used for internal libs.

Generic:
[ ]: Package is licensed with an open-source compatible license and meets
     other legal requirements as defined in the legal section of Packaging
以下省略  
specファイルやSRPMのアップロードの注意事項

Join the package collection maintainersでは、Upload Your Packageというセクションで、specやSRPMのアップロード先としてfedorapeople.orgを紹介しています。 しかし、最初のレビューリクエストを投稿する時点では、fedorapeople.orgへファイルをアップロードする権限がありません。

fedorapeople.orgへのアクセスするには、自分のFASアカウントを後述するpackagerグループに追加してもらう必要がありますが、これはパッケージのレビューが承認された後になるからです。

そのため、この時点ではどこか他の場所にspecやSPRMをアップロードして、レビューしてもらえる状態にする必要があります。

レビューリクエストの投稿

specファイルとSRPMファイルが用意できたら、Bugzillaへレビューリクエストを投稿します。

レビューを投稿するときの雛形は、専用の入力フォームとしてfedora-reviewがあります。

レビュー雛形画面

Spec URL: <spec info here>
SRPM URL: <srpm info here>
Description: <description here>
Fedora Account System Username:

Cutterの場合は以下のようなレビューリクエストを投稿しました。

Spec URL: http://sourceforge.net/projects/cutter/files/tmp/cutter.spec
SRPM URL: http://sourceforge.net/projects/cutter/files/tmp/cutter-1.2.2-1.fc17.src.rpm

Description: 
Cutter is a xUnit family Unit Testing Framework for C.
Cutter provides easy to write test and easy to debug code environment.
Fedora Account System Username: kenhys
  • Spec URL: SPECファイルのURLを記述します。
  • SRPM URL: SRPMファイルのURLを記述します。
  • Description: パッケージの説明を記述します。
  • Fedora Account System Username: ここにあらかじめ取得したFASアカウント名を記述します。
レビューリクエストの注意事項

レビューリクエストを行う際には、以下の点に注意する必要があります。

  • FE-NEEDSPONSORフラグの設定
  • fedora-reviewフラグの設定

新規パッケージを投稿するときには、FE-NEEDSPONSORフラグを設定しなければなりません。

これはパッケージのメンテナンスの権限を有するpackagerグループのうち、より高度な権限を持つsponsorという権限を持っている人にレビューをしてもらう必要があることを示します。

FE-NEEDSPONSORフラグは、レビューリクエストの左側にあるBlocks:という項目を編集します。

FE-NEEDSPONSORフラグ

fedora-reviewフラグを設定するには、レビューリクエストのFlags:という項目を編集します。

Flags(edit)

Flags:は登録したレビューリクエスト画面の右側に項目があります。

Flags:の隣りの(edit)をクリックするといくつかプルダウンが表示されます。

Flags

このうち、fedora-reviewという項目があるので、プルダウンから?を選択して更新します。 fedora-reviewに設定できる値とその意味は以下の通りです。

fedora-reviewフラグ説明
?レビューリクエスト中
+レビューが承認された
-レビューが却下された

FE-NEEDSPONSORが設定されているレビューリクエストのみを一覧で参照することもできます。

レビューリクエストの承認

レビューリクエストを投げたものの、それがそのまますんなり通るとは限りません。

Cutterの場合もレビューリクエストを最初に投げてから何度かspec記述上の問題点をレビュワーに指摘され、修正するというサイクルを繰り返しました。

レビュワーは、指摘事項の根拠となるドキュメントを示しながら、問題点をどう修正すべきかをコメントしてくれます。

例えば、Cutterの場合だと、指摘された問題点の一つにBuildRoot:タグを指定していたというものがありました。

> BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n)

https://fedoraproject.org/wiki/Packaging:Guidelines#BuildRoot_tag

The "fedora-review" tool complains about this tag, too.

すべての指摘事項を修正し、レビューを通ると以下の状態になります。

  • レビュワーがレビューリクエストにAPPROVEDというレビューコメントをつける(レビュワーによって表現は多少異なる場合があります)。
  • レビュワーがレビューリクエストのfedora-reviewフラグを+に変更する。
  • レビュワーがレビューリクエストした人をpackagerグループに追加する。

packagerグループに入るとFedoraのサーバーへアクセスできるようになります*7

fedora-reviewフラグが+になったら、次のステップ「SCMリクエスト」に進みます。

SCMリクエストについて

パッケージのレビューが通ったら次にやることは、新規パッケージのためにリポジトリを用意することです。

リポジトリについては、レビューリクエストのバグのコメントにSCMリクエストと呼ばれる作業依頼のコメントを投稿します。

Cutterの場合は以下のようなSCMリクエストを行いました。

New Package SCM Request
=======================
Package Name: cutter
Short Description: Unit Testing Framework for C/C++
Owners: kenhys
Branches: f18
InitialCC:
  • Package Name: パッケージ名を記述します。
  • Short Description: パッケージの説明を記述します。specのSummary:を書くなどすると良いでしょう。
  • Owners: 自身のFASアカウントを記述します。
  • Branches: リポジトリに追加するブランチを記述します。Cutterは当時のリリースであるFedora18(f18)ブランチのみ指定しました。
  • InitialCC: 他にもメンテナとして追加したい人のFASアカウントを記述します。

SCMリクエストを行ったあとは、しばらく待つ必要があります。 Cutterの場合は一日程度でした。 リポジトリを作成する権限のある人が、定期的にSCMリクエストをチェックし、順次対応してくれているようです。

SCMリクエストが処理され、リポジトリの準備が完了すると、レビューリクエストに以下のようなコメントが書き込まれます。

Git done (by process-git-requests).

またfedora-cvs granted:という件名で、通知メールがあらかじめ登録しておいたアカウントに届きます。

これで、specとSRPMをコミットするためのリポジトリが準備できました。

次のステップ「パッケージのインポート」へと進みます。

これ以降の作業(「パッケージのインポート」から「パッケージのアップデート」まで)は、master(rawhide)*8とSCMリクエストで指定したブランチごとにくりかえす必要があります。

作業しているブランチによって一部手順が異なりますが、詳細については「ブランチごとの作業まとめ」として後述します。

パッケージのインポート

パッケージのインポートでは以下の作業が必要です。

  • SSHアクセスの確認
  • fedpkgのインストール
  • リポジトリのclone
  • SRPMのインポート

以下、順に説明していきます。

SSHアクセスの確認

レビューが通った後、レビュワーによりpackagerグループに追加してもらえます。 packagerグループに所属しているとFedoraのサーバーへとアクセスすることができます。

packagerグループに所属しているかどうかは、FASの"My Account"メニューを表示したときに表示される所属グループリストの"Fedora Packager Git Commit Group"でわかります。ここのStatus欄が"Approved"になっていると、packagerグループに所属していることになります。

Fedora Packager Git Commit Group

以降の作業ではサーバーへのアクセスをともなうので、FASアカウントを作成したときに用意した秘密鍵を用いて、SSHでログインできるか確認します。

$ ssh-add 秘密鍵のファイルパス
$ ssh -vvv -l (FASアカウント名) fedorapeople.org

SSHアクセスに成功したら、実際にパッケージのインポートを順に行っていきます。

fedpkgのインストール

Fedoraプロジェクトではパッケージのビルドやアップロードなどのメンテナンス作業にfedpkgというコマンドを使います。

fedpkgコマンドを使うには、あらかじめfedpkgパッケージをインストールする必要があります。

前述のKojiによるビルド確認を試している場合は、fedora-packagerのインストール時に、既に(パッケージの依存関係により)fedpkgをインストールしているはずです。 そうでない場合には以下の手順でインストールします。

$ sudo yum install fedpkg

fedpkgのインストールが完了したら、次のステップ「リポジトリのclone」に進みます。

リポジトリのclone

まずは作業用のリポジトリをcloneします。 作業用のディレクトリを用意して、そこにcloneするのが良いでしょう。

$ fedpkg clone (パッケージ名)

上記コマンドを実行することでリポジトリをcloneすることができます。

Cutterの場合は以下のように作業ディレクトリを用意してcloneしました。

$ mkdir -p $HOME/work/fedora-scm
$ cd $HOME/work/fedora-scm
$ fedpkg clone cutter
$ cd cutter
SRPMのインポート

リポジトリのcloneができたら、SRPMを以下のコマンドにてインポートします。

$ fedpkg import (SRPMのパス)

fedpkg importコマンドは、SRPMからspecファイルやパッチ、ソースアーカイブを抽出し、作業ディレクトリへと展開します。 また、.gitignoreやハッシュ値を書きこんだsourcesファイルも生成します。

Cutterの場合は以下のようになりました。

$ ls -1
cutter-1.2.2.tar.gz
cutter-configure-gtk-debug-detection.diff
cutter-test-use-upper-case-gdk-literal.diff
cutter.spec
sources

ここまでで、パッケージのインポートが完了しました。

次のステップ「パッケージのコミット」へと進みます。

パッケージのコミット

パッケージのインポートが完了したら、差分(新規追加)を確認した上でコミットします。 また、pushしてローカルのコミット内容をアップストリームへと反映します。

$ git commit -m "Initial import (#XXXXXX)." 
$ git push

pushが完了したら、次のステップ「パッケージのビルド」へと進みます。

パッケージのビルド

パッケージのビルドを行うにはfedpkg buildを実行します。

CutterでFedora 18ブランチ向けにビルドしたときは、以下のようになりました*9

$ fedpkg build
Building cutter-1.2.2-4.fc18 for f18-candidate
Created task: 4941489
Task info: http://koji.fedoraproject.org/koji/taskinfo?taskID=4941489
Watching tasks (this may be safely interrupted)...
4941489 build (f18-candidate, /cutter:5978273caa55d3d45ed8e760efc15919b6133ab9): free
4941489 build (f18-candidate, /cutter:5978273caa55d3d45ed8e760efc15919b6133ab9): free -> open (buildvm-17.phx2.fedoraproject.org)
  4941490 buildSRPMFromSCM (/cutter:5978273caa55d3d45ed8e760efc15919b6133ab9): free
  4941490 buildSRPMFromSCM (/cutter:5978273caa55d3d45ed8e760efc15919b6133ab9): free -> open (buildvm-16.phx2.fedoraproject.org)
  4941490 buildSRPMFromSCM (/cutter:5978273caa55d3d45ed8e760efc15919b6133ab9): open (buildvm-16.phx2.fedoraproject.org) -> closed
  0 free  1 open  1 done  0 failed
  4941492 buildArch (cutter-1.2.2-4.fc18.src.rpm, i686): free
  4941491 buildArch (cutter-1.2.2-4.fc18.src.rpm, x86_64): open (buildvm-18.phx2.fedoraproject.org)
  4941492 buildArch (cutter-1.2.2-4.fc18.src.rpm, i686): free -> open (buildvm-07.phx2.fedoraproject.org)
  4941492 buildArch (cutter-1.2.2-4.fc18.src.rpm, i686): open (buildvm-07.phx2.fedoraproject.org) -> closed
  0 free  2 open  2 done  0 failed
  4941491 buildArch (cutter-1.2.2-4.fc18.src.rpm, x86_64): open (buildvm-18.phx2.fedoraproject.org) -> closed
  0 free  1 open  3 done  0 failed
  4941510 tagBuild (noarch): free
  4941510 tagBuild (noarch): free -> open (buildvm-21.phx2.fedoraproject.org)
  4941510 tagBuild (noarch): open (buildvm-21.phx2.fedoraproject.org) -> closed
  0 free  1 open  4 done  0 failed
4941489 build (f18-candidate, /cutter:5978273caa55d3d45ed8e760efc15919b6133ab9): open (buildvm-17.phx2.fedoraproject.org) -> closed
  0 free  0 open  5 done  0 failed
4941489 build (f18-candidate, /cutter:5978273caa55d3d45ed8e760efc15919b6133ab9) completed successfully

ビルドが成功したので、次のステップ「パッケージのアップデート」へと進みます。

パッケージのアップデート

Fedoraプロジェクトではパッケージの追加/更新/削除やパッケージの公開・管理を行うためのシステムとしてBohdiを使っています。

BohdiはビルドしたパッケージをFedora ユーザーが参照するyumリポジトリへと反映します。

Bohdiへビルドしたパッケージを反映させるには、パッケージのアップデートを行います。

パッケージのアップデートをするには以下のコマンドを実行します。

$ fedpkg update

fedpkg updateを実行するとエディタが起動するので、アップデートに関する項目を編集します。 何のパッケージの、どんな更新なのかといったメタ情報を記述します。

必須なのはtypeで、新規パッケージの場合にはnewpackageを指定します。

Cutterの場合は以下のようにしました。

[ cutter-1.2.2-4.fc18 ]

# bugfix, security, enhancement, newpackage (required)
type=newpackage

# testing, stable
request=testing

# Bug numbers: 1234,9876
bugs=887778

# Description of your update
notes=Unit Testing Framework for C/C++

# Enable request automation based on the stable/unstable karma thresholds
autokarma=True
stable_karma=3
unstable_karma=-3

# Automatically close bugs when this marked as stable
close_bugs=True

# Suggest that users restart after update
suggest_reboot=False

編集結果を保存するとfedpkg updateコマンドがその内容をBohdiへと送ります。

Creating a new update for  cutter-1.2.2-4.fc18 
Password for kenhys: 
Creating a new update for  cutter-1.2.2-4.fc18 
Update successfully created
================================================================================
     cutter-1.2.2-4.fc18
================================================================================
    Release: Fedora 18
     Status: pending
       Type: newpackage
      Karma: 0
    Request: testing
       Bugs: 887778 - Review Request: cutter - A Unit Testing Framework for C
      Notes: Unit Testing Framework for C/C++
  Submitter: kenhys
  Submitted: 2013-02-09 10:07:19
   Comments: bodhi - 2013-02-09 10:07:47 (karma 0)
             This update has been submitted for testing by kenhys.

  https://admin.fedoraproject.org/updates/cutter-1.2.2-4.fc18
ブランチごとの作業まとめ

「パッケージのインポート」から「パッケージのアップデート」までは作業しているブランチごとに繰り返す必要があります。 ただし、作業ブランチによっては、やるべきこと、やらなくていいことがあります。

そこで、ここまでの作業手順を一旦まとめます。Cutterの場合には(当時Fedora 19ブランチはなかったので)、masterブランチとFedora 18ブランチについて作業しました。

作業masterブランチFedora 18ブランチ
パッケージのインポート必要SRPMのインポートは不要。代わりにmasterブランチからマージする。
パッケージのコミット必要必要(pushのみ)
パッケージのビルド必要必要
パッケージのアップデート不要必要
パッケージのテスト不要必要
パッケージのリリース不要必要

まず、masterブランチ向けに「パッケージのインポート」から「パッケージビルド」まで行いました。 次にfedpkg switch-branch f18でFedora 18のブランチに切り替え、masterブランチからマージした後、パッケージのリリースまで行いました。

上記を具体的な実行コマンド列で比較すると以下のようになります。

masterブランチの場合

$ git import (SRPMのパス)
$ git commit
$ git push
$ fedpkg build

Fedora 18ブランチの場合

$ fedpkg switch-branch f18
$ git merge master
$ git push
$ fedpkg build
$ fedpkg update

Fedora 18ブランチの手順では以下のことを行っています。

  • fedpkg switch-branch f18でrawhideと呼ばれるmasterブランチからf18ブランチに切り替える
  • git merge masterでmasterブランチの修正をf18ブランチにとりこむ
  • git pushでコミットをアップストリームに反映する
  • fedpkg buildでf18向けにパッケージをビルドする
  • fedpkg updateでf18向けにビルドしたパッケージをBohdiへと反映する

ここまでの作業がブランチごとに完了したら、次のステップ「パッケージのテスト」へと進みます。

パッケージのテスト

fedpkg updateでBohdiへとパッケージを更新するようにしむけても、それで終わりではありません。

投稿したRPMパッケージが一般ユーザーが利用しているyumリポジトリへと反映されるまでには、いくつかの段階があります。

Bohdiのステータス説明
pendingリポジトリ反映前
testingtestingリポジトリへ反映されている
stable一般ユーザーが利用するyumリポジトリへ反映されている

RPMパッケージは一旦キューに入れられるのでpending状態になり、その後testingリポジトリ*10へと反映されます。

そしてtestingリポジトリで一定評価を得たパッケージがstable*11に入る仕組みになっています。

その際の評価はKarmaと呼ばれる仕組みによって行います。

Bohdiにアカウントを有しているユーザーは、Webインターフェースからパッケージに対するコメントをすることができます。 コメントするときには、あわせてパッケージを評価することができます。 この評価欄の集計がKarmaです。

ユーザーがパッケージに対してコメントするときに 「Works for me」にチェックを入れると+1となり、「Does not work」にチェックを入れると-1の評価となります。

Bohdiコメント欄

デフォルトのKarmaは0からスタートします。 BohdiはKarmaが+3になったパッケージをstableリポジトリへと反映し、逆に-3に達したパッケージをtestingリポジトリから削除します。

testing状態のCutter

このKarmaの閾値についてはfedpkg update実行時にその都度メタ情報として設定できます*12

この仕組みの詳細については、QA:Updates Testingを参照してください。

パッケージのリリース

一定評価(stable_karma)を得られたパッケージは、Bohdiが自動的に一般ユーザーが利用するyumリポジトリ(stableリポジトリ)へと反映します。

一定評価を得られていないパッケージであっても、一定期間(2週間程度)経過すると、手動操作でyumリポジトリへと反映することができます。

その場合は、パッケージをアップロードした人がBohdiのインターフェースから作業します。 該当パッケージの情報ページで「Mark as stable」をクリックするとyumリポジトリへ反映することができます。

mark as stable

まとめ

新規パッケージをリリースするまでに必要な情報については、すでにFedoraプロジェクトによって文書化されています。 また、一部には日本語訳も用意されています。

ただし、実際にやってみないことにはわからないこともあります。

そこで今回は具体例をもとに実際にパッケージをリリースするところまでの流れを紹介しました。

フリーソフトウェアの開発元がかならずしも使っているディストリビューション向けにRPMパッケージを提供してくれるとは限りません。

ソースアーカイブからRPMを作成してくれるcheckinstallなどもあるので、手元の環境ではRPM化して管理しているという人もいるかも知れません。

しかし、自作RPMパッケージを手元でだけ管理している状態では、他の人もRPM化して管理したい場合には、また同様の手順を踏むことになります。 自分は問題を解決したけど、また再度同じ問題にぶつかる人がいるという状況に変化はありません。

成果物については手元の環境だけで眠らせておかずにディストリビューションへと還元するのをおすすめします。

今回は、新規にパッケージを登録するところまでを書きました。 パッケージは登録して終わりではありません。継続してメンテナンスしていくことも重要です。

既存パッケージを更新する方法については、またの機会に記事にしたいと思います。

参考:パッケージメンテナンス関連の文書

*1 1.2.2をリリースして以降。

*2 rpmlintによりスペルミスとして警告されていても、固有名詞のため問題ない場合もあります。また、rpmlintはそれぞれのRPM単位でのチェックのため、メインパッケージに含まれていれば良いライセンス類がサブパッケージに含まれていないと警告として扱われたりします。

*3 例えば現在リリースされているFedora 18だけではなく、リリース前のFedora 19などでもビルド確認が行えます。

*4 fedora-browser-cert.p12についてはブラウザ経由でKojiへアクセスするときに必要です。セットアップ手順は上記のメッセージの通り証明書のインポートを行います。

*5 証明書の有効期限が切れたら更新する必要はあります。

*6 BuildRequires:の指定が抜けているなど。

*7 例えば、fedorapeople.orgに割り当てられたディスク領域を使うことができます。次回以降のレビューリクエストを行うときはこちらにspecファイルとSRPMを置くと良いでしょう。

*8 RawhideはFedoraの開発版を意味し、masterブランチのことです。

*9 masterブランチのビルドのときのログが残っていなかったのでFedora 18向けにビルドしたときのログです。

*10 testingリポジトリは、テスター向けのリポジトリです。標準では有効になっていないので、testingリポジトリのパッケージを実際にインストールするには別途有効にする必要があります。

*11 一般ユーザーが利用するyumリポジトリ

*12 stableリポジトリへ反映される基準値はstable_karmaで、削除される基準値はunstable_karmaで設定できます。それぞれデフォルト値は3と-3です。

2013-04-10

Rubyで定義したメソッドに戻り値のYARD用ドキュメントを書く方法

はじめに

YARDというRuby用のドキュメンテーションツールがあります。これまでに2回、YARD用のドキュメントの書き方についてククログで紹介しました。

今回は前回の「Rubyで定義したメソッドの引数についてのドキュメントをYARD用に書く方法」という記事の続きで、Rubyで定義したメソッドに戻り値のYARD用ドキュメントを書く方法を紹介します。

まず、戻り値のYARD用ドキュメントを書くための@returnタグ*1について説明したあと、実際にドキュメントを書くRubyのコードを示します。その後、実際に@returnタグを使ってドキュメントを書き、YARDのコマンドを使ってHTMLのリファレンスマニュアルを作成します。

なお、YARDについてや、YARDのタグの使い方、HTMLのリファレンスマニュアルの生成の仕方については前回の記事を参照してください。

@returnタグについて

まず、@returnタグについて説明します。@returnタグはYARDのタグ(コードのメタデータを記述するための記法)の1つで、戻り値についての説明を書くためのタグです。他のタグと同様にコメントの中に書くことでYARDのタグとして認識されます。

@returnタグの書式は以下の通りです。

@return [戻り値のクラス] 説明

詳しい書き方は実際に書くときに説明します。では次に、実際にドキュメントを書くRubyのコードを示します。

戻り値のドキュメントを書くRubyのコード

戻り値のドキュメントを書くRubyのコードは、前回の記事にて、メソッド全体の説明と引数についての説明を書いたコードを使います。

1
2
3
4
5
6
7
8
9
10
11
# Returns Array containing number exceeding limit_age.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
#
# @param ages [Array] numbers checked if they exceeded from limit_age.
# @param limit_age [Integer] limit_age limit used to extract
#   bigger ages than it.
def extract_overage(ages, limit_age=20)
  ages.select do |number|
    number > limit_age
  end
end

このコードを、以降は「サンプルコード」と呼びます。サンプルコードについての説明を前回の記事から再掲します。

サンプルコードはextract_overageメソッドを定義しています。extract_overageメソッドは、配列と数値を引数にとり、各要素から数値より大きい数(数値が指定されない場合は20より大きい数)を集めた配列を返します。例えば、extract_overage([17, 43, 40, 56], 40)を実行すると、[43, 56]を返します。

サンプルコードにはすでに、メソッド全体の説明(コメントの最初から2行目)と、引数についての説明(コメントの4行目から6行目)が書かれています。これは前回の記事で書いたドキュメントをそのまま使っています。メソッド全体の説明と引数全体の説明の書き方については、前回の記事を参照してください。

前回と同様に、この記事ではドキュメントの書いてある場所をわかりやすくするために、シグニチャーの部分だけを抜き出したものを示します。

1
2
3
4
5
6
7
8
# Returns Array containing numbers exceeding limit_age.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
#
# @param ages [Array] numbers checked if they exceeded from limit_age.
# @param limit_age [Integer] limit_age limit used to extract
#   bigger ages than it.
def extract_overage(ages, limit_age=20)
end

なお、この状態でHTMLのリファレンスマニュアルを作成すると次のようになります。

メソッド全体の説明と引数についての説明が追加されたリファレンスマニュアル

メソッド全体の説明(画像の赤枠の部分)と、@paramタグで書かれた説明(画像の緑の枠の部分)がHTMLのリファレンスマニュアルに表示されているのがわかります。今回はこのサンプルコードのシグニチャーに、@returnタグを使って戻り値についてのドキュメントを書きます。

@returnタグの使い方

@returnタグの書式を再掲します。

@return [戻り値のクラス] 説明

@returnタグの書式について説明します。戻り値のクラスのところには、戻り値のオブジェクトのクラスを示す任意の文字列を書きます。説明のところには、文章で戻り値の説明を書きます。例えば、今回の戻り値は配列なので、配列の中身について*2説明した文章を書きます。

また、1つのメソッドにつき@returnタグは複数個書けるので、戻り値の値が変わる場合はその数だけ書くのがよいでしょう。例えば、Arrayクラスのshiftメソッドは、レシーバー*3の配列が空でないときはその先頭の要素を返しますが、空の場合はnilを返します。このように戻り値が異なる場合は、戻り値が先頭の要素の時とnilの時についてそれぞれ@returnタグを書きます。そうすると1つ1つの戻り値についての説明が分けて書かれるため読みやすくなります。この時、説明のところにはメソッドがその戻り値を返す条件を書くと、それぞれの戻り値がどんな時に返されるのかがわかりやすくなります。

なお、@returnタグを書く場所ですが、@paramタグの下がよいでしょう。なぜかというと、引数の説明の下に戻り値の説明が書いてあるとすんなり読めるからです。メソッドは引数を受け取り、その引数を使って処理をして、戻り値を返します。それと同様に、ドキュメントも引数、戻り値の順に配置しておくと、「引数を元にこのメソッドが戻り値が生成する」とメソッドの処理の流れと同じようにすんなり読めます*4

それでは実際に書いてみます。サンプルコードのシグニチャーに@returnタグを使ってドキュメントを書いたのが次のコードになります。

1
2
3
4
5
6
7
8
9
# Generates Array containing numbers exceeding limit_age. Numbers is members of ages.
# For example, extract_overage([17, 43, 40, 56], 40) #=> [43, 56]
#
# @param ages [Array] numbers checked if they exceeded from limit_age.
# @param limit_age [Integer] limit_age limit used to extract
#   bigger ages than it.
# @return [Array] Returns Array containing numbers exceeding limit_age.
def extract_overage(ages, limit_age=20)
end

戻り値は配列なので、戻り値のクラスのところにはArrayを書きます。@returnタグで戻り値の説明は、メソッド全体の説明から該当する部分を抜き出して書きました。それに伴ってメソッド全体の説明も修正しました。このコードをexample.rbというファイルに保存し、次のコマンドでHTMLのリファレンスマニュアルを生成します。

$ yardoc example.rb

生成されたHTMLのリファレンスマニュアルは次のようになります。

戻り値の説明が追加されたリファレンスマニュアル

図の赤枠の部分に戻り値についての説明があるのがわかります。また、図の緑色の部分2ヶ所には、@retrunタグで書いた戻り値のクラスであるArrayが表示されているのがわかります。

まとめ

今回は、前回の引数についてのYARD用ドキュメントを書く方法からの続きで、戻り値についてのYARD用ドキュメントを書く方法を説明しました。前回と今回の記事の内容を使うと、メソッドについての基本的な説明をYARDで書くことができます。

次は例を書くために使う@exampleタグについて説明します。

*1 詳細は後述します。

*2 今回のサンプルコードでは、指定したlimit_ageよりも大きい数値を集めた配列であること

*3 メソッドの操作対象となるオブジェクト

*4 なお、HTMLのリファレンスマニュアルではどちらを先に書いても引数の説明が先に表示されます。

タグ: Ruby
2013-04-18

わかりやすいコミットメッセージの書き方

もう1年以上前になりますが、コミットメッセージの書き方を説明しました。ざっくりまとめると、以下のことを説明しています。

  • わかりやすいコミットメッセージがいかに大切か
  • どのようなコミットメッセージがわかりやすいか(具体例付き)

この説明をしてからも、日々コミットしていくなかで新たに得られた「どうすればもっとわかりやすいコミットメッセージになるか」という知見が増えていました。これは、コミットへのコメントサービスの提供を開始した*1ことも影響しています。このサービスでは、コミットへコメントするときに「どうして自分は他の書き方よりもこの書き方をわかりやすいと感じるか」を説明しています。その過程で「なんとなくこっちの方がよさそう」だったものを「具体的にこういうときにこう感じるのでこっちの方がよさそう」と何かしら理由を考えるようになりました。これにより、今までそれぞれの開発者でなんとなくだった考えが共有でき、チーム全体として「では○○という理由があるからこっちの書き方にしよう」と、考えを共有した上で自分達のスタイルを作っていくことができるようになりました。

今回はそんな日々の開発の中で明文化されたわかりやすいコミットメッセージの書き方を紹介します。理由もつけて紹介するので、自分達のチームに取り入れるときは、自分達にとっても本当にわかりやすいかどうかを検討してから判断してください。

付加情報を書く

コミットメッセージをわかりやすくするための1つの方法として、「○○しました」だけではなく、どうして○○をしたのかなどの情報を入れるという方法があります。しかし、なんでもいれればよいというものではありません。長すぎるコミットメッセージは読む気が失せてしまうからです。それではどのような情報は入れた方がよいのでしょうか。

revertした理由を書く

git revertをすると自動で「このコミットをrevertします」というログが入ります。この状態でコミットしている人は結構多いのではないでしょうか。

それでは、このrevertコミットを読む人のことを考えてみましょう。「どうしてこのコミットがrevertされたのか」ということが気になるはずです。revertしたときはrevertした理由を付加情報として書きましょう。

例えば、以下のrevertコミットは必要なファイルを追加し忘れたのでコミットしなおすためにrevertしています。

commit a0b6d964ef41f85bb825928813a67533da3d4663
Author: Kouhei Sutou <kou@clear-code.com>
Date:   Tue Mar 19 19:05:18 2013 +0900

    Revert "doc: update for renaming --query_expansion to --query_expander"

    This reverts commit 4837201f9fd01826c4dd21ced2b3f357a9e9bfcd.

    I forgot to add renamed files. Sorry...
実行したコマンドを書く

定形処理は自動化して誰でも簡単に間違いなく実行できるようにしていますよね。そのような自動化された処理を実行して一括で変更した内容をコミットする場合は、実行したコマンドを付加情報として書きます。

以下は、バージョン番号を更新したコマンドを付加情報として書いている例です。

commit 6a27a9f890453bf4f08c972ee53b2a40ba066489
Author: HAYASHI Kentaro <hayashi@clear-code.com>
Date:   Tue Apr 23 11:48:41 2013 +0900

    Bump up version number to 3.03

    make update-version NEW_VERSION_MAJOR=3 NEW_VERSION_MINOR=0 NEW_VERSION_MICRO=3

コマンドを書いておけば、他の人が同じことをやろうとしたときにヒントになるため、役立ちます。

英語だけで表現することにこだわらない

以前の記事では「コミットメッセージは英語で書きましょう」と説明していました。しかし、英語で表現することにこだわりすぎるのも考えものです。以前の記事で英語で書こうと説明していた理由は、多くの人が読めるからです。つまり、多くの人にわかりやすく伝えるために英語という手段を使いましょうということでした。もし、英語よりも多くの人にわかりやすく伝えられるのであれば、英語にこだわらずにその表現を使いましょう。

「どう変わったか」を伝える場合

「どう変わったか」を伝える場合は比較対象を縦に並べます。

例えば、CommitCommentTools::CommitsAnalyzerクラスのanalyzeメソッドをparetoメソッドにリネームしたとします。この場合は英語にすると以下のようになります。

Rename analyze method to pareto method of CommitCommentTools::CommitsAnalyzer class

この中で大事なことは「analyze」と「pareto」の対比です。英語で表現すると、読むとわかりますが、パッとみただけでは、そこまで目立ちません。では、どのように表現すればよいでしょうか。

テスティングフレームワークを使って開発をしたことがある人はassert_equalassertEqualsshould ==などが失敗したときにどのように表示されるかを思い出してください。以下のように期待値と実際の値を並べて表示しますよね。

expected: <analyze>
 but was: <pareto>

何かを比較するときは横ではなく縦に並べた方が違いがパッとみてわかりやすいのでこうなっています。例えば、「1文字目から違う」ということもわかりますし、「そもそも文字の長さが違う」ということもパッとみてわかります。

これをコミットメッセージに応用するとこうなります。

Rename method of CommitCommentTools::CommitsAnalyzer class

analyze ->
pareto

「どう変わったか」が大事なコミットメッセージを書くときは比較対象を縦に並べましょう。比較しているものが明確になりますし、違いもわかりやすくなります。

「少しだけ変わったこと」を伝える場合

縦に並べればなんでも違いがわかりやすくなるわけではありません。例えば、typoの修正は縦に並べても見つけることは大変です。

そんなとき、ひと手間かけるとグッとわかりやすくなります。

commit 27df8ff1fd26187bbc8e2817b633c31932559f18
Author: Haruka Yoshihara <yoshihara@clear-code.com>
Date:   Thu Apr 4 11:02:17 2013 +0900

    test: fix a typo

    TestDefaultPathRuels ->
    TestDefaultPathRules
                     ^^

違っている箇所の下に「^」で印をつけます。これがあれば、読む人はここに注目すればよいということが一目瞭然なのでわかりやすくなります。直した人はどこが違っていたかをすでに知っているためできるテクニックです。

なお、違っている桁の下に「^」をつけるのはPythonのdifflibで使われているスタイルです。

「使い方が変わったこと」を伝える場合

リファクタリングなどでより便利な書き方をできるようにすることはよくあることです。それを伝えたい場合も必ずしも英語にこだわる必要はありません。

例えば、以下のようにGroonga::Contextnewして自分でcloseしていたものを、ブロック内で実際の処理を行い、ブロックを抜けたら自動でcloseするようにしたとします。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
diff --git a/lib/fluent/plugin/out_droonga.rb b/lib/fluent/plugin/out_droonga.rb
index 1baea60..6979c43 100644
--- a/lib/fluent/plugin/out_droonga.rb
+++ b/lib/fluent/plugin/out_droonga.rb
@@ -83,21 +83,30 @@ module Fluent
     def ensure_database
       return if File.exist?(@database)
       FileUtils.mkdir_p(File.dirname(@database))
-      context = Groonga::Context.new
+      create_context do |context|
       context.create_database(@database) do
       end
-      context.close
+      end
     end

     def ensure_queue
-      context = Groonga::Context.new
+      create_context do |context|
       context.open_database(@database) do
         Groonga::Schema.define(:context => context) do |schema|
           schema.create_table(@queue_name, :type => :array) do
           end
         end
       end
-      context.close
+      end
+    end
+
+    def create_context
+      context = Groonga::Context.new
+      begin
+        yield(context)
+      ensure
+        context.close
+      end
     end

     def load_handlers

この場合は、今、日本語で説明したことを英語で説明するのではなく、実際にどのように使い方が変わったかをコードで表現しましょう。コミットメッセージを読む人の多くは開発者です。開発者の多くは英語だけではなく、もちろん、コードのこともわかっているはずです。コードのことはコードで伝えましょう。その方が間違いなく伝わります。

commit badf11498eb38beaa6b104e0f51fb3eb3b27a331
Author: Kouhei Sutou <kou@clear-code.com>
Date:   Thu Apr 4 14:54:04 2013 +0900

    Extract common create and close context code

    Before:

        context = Context.new
        ...
        context.close

    After:

        create_context do |context|
          ...
        end

コミットメッセージを書く上で大事なことは、英語で書くことではなく「多くの人にわかりやすく情報を伝えること」です。そのために英語で書くという手段を使っているのです。英語で書くことだけにこだわらず、本来目指していた「多くの人にわかりやすく情報を伝えること」を実現するにはどうしたらよいかを考えてみてください。

英語で書く

英語以外で表現した方がわかりやすいケースを紹介しましたが、英語の方がわかりやすいケースもあります。どのような表現がよいか「わかりやすさ」という観点で考えてみてください。

文字列リテラルのクォートをダブルクォートに変更した場合

文字列リテラルに「'」(シングルクォート)あるいは「"」(ダブルクォート)のどちらも使えるプログラミング言語がいくつもあります。例えば、Rubyやシェルもそのような言語です。

同じソース内では表記を統一していた方が見通しがよくなります。そのため、「"」(ダブルクォート)に統一したとします。そのとき考えられるコミットメッセージには以下のようなものがあります。

Use "XXX" style string literal
Use " for string literal
Use '"' for string literal

最初のケースでは「"XXX"」で文字列リテラル一般を表現しようとしています。つまり、コードで表現してわかりやすくしようとしています。コードと同じ表記なのでわかりやすくなる人がいる一方で、「XXX」を強調している英語での書き方と見えてしまう人もいるため、かえってわかりにくくなる危険があります。

2つめは「"」そのものを書いています。ソース中では「double quote」と書くよりも「"」と書いていることの方が多いため、ソース中でよく見る字面にあわせてわかりやすくしようとしています。これもコードと同じ表記なのでわかりやすくなる人がいる一方で、「"」を英語での文脈で解釈してしまうと閉じる「"」を探してしまうことになり、かえってわかりにくくなる危険があります。

3つめは「"」だけだとわかりにくいため、「'"'」と「"」を強調しています。ただ、「'"'」と「"'"」で区別がつきにくいので「"」のことなのか「'」のことなのかがかえってわかりにくくなる危険があります。

このように違う意味で解釈される危険性を回避するために、英語表記も併記します。

commit 904e124f3459df3ee9138de5fef65450613c4058
Author: Kouhei Sutou <kou@clear-code.com>
Date:   Sun Apr 14 11:02:17 2013 +0900

    Use " (double quote) for string literal

こうすれば、「"」をプログラムの文脈で読んでパッとみて理解できるケースと、「"」が何のことかわからなかったケースもどちらにも対応できます。

ダブルクォートで囲んだ場合

シェルでは文字列をクォートで囲む必要はありません。しかし、文字列内に空白が入るかもしれないケースを考慮するとクォートで囲んでおいた方が無難です。

そんなときのコミットメッセージとしてコード片を使うことを考えると、例えば以下のようになります。

Add missing "..."

この書き方では、「"..."」という文字列リテラルになっていなかったのでそうしたというのを表現しようとしています。しかし、「...」が抜けていたので追加した、というようにも読めてしまいます。

この場合はクォートしたと英語で書いた方が伝わるでしょう。

Quote string expression

まとめ

コミットへのコメントサービスや日々の開発の中で試行錯誤してわかってきた、わかりやすいコミットメッセージの書き方を例と理由をあわせて紹介しました。理由もつけてあるので、自分達のチームに取り入れるかどうかは理由を検討した上で判断してください。

*1 現在、1社に提供しており、近いうちに導入事例を紹介できる見込みです。

2013-04-24

るりまをより便利にするために開発したい機能

こんにちは。クリアコードで組込み機器向けのサイネージシステムの開発やRubyでのmilter*1開発などを担当している沖元です。プライベートでは、るりまプロジェクトなどで活動しています。

先日このブログで紹介したインターンシップ制度では、クリアコードのメンバーが開発したいと考えているフリーソフトウェアの中から「これは」というものをインターンが選択します。本エントリで紹介する開発したいフリーソフトウェアはBitClust*2です。BitClustに以下の機能を追加してるりまをより便利にしたいと考えています。

  • RDocへのリンクを表示する機能
  • サンプルコードの実行結果をデータベース生成時に埋め込む機能

各機能の開発内容の詳細を述べます。

RDocへのリンクを表示する機能

概要

Ruby-Doc.orgへのリンクを表示できるようにします。RDocの各クラスやメソッドへのリンクは機械的に計算することができるはずなので、そんなに難しくないと考えています。

この機能を追加するのは、るりまの内容とRDocの内容をウェブブラウザ上で簡単に比較できるようにするためです。また、次のような効果も期待しています。

  • るりまのページからRDocの内容をすばやく確認できると正しい情報に到達しやすくなる。
  • るりまの内容とRDocの内容を簡単に比較できるようになるので、RDocに不足がある場合でもRDocへのフィードバックがしやすくなる。
開発の流れ(現時点での想定)
  1. チケットを読む。
  2. RDocの各クラスやメソッドへのリンクの作成方法を検討する。(RDocのソースコードを読む必要があるかもしれません。)
  3. クラス名やメソッド名からRDocへのリンクを生成するヘルパーメソッドをBitClustに追加する。
  4. 上で追加したヘルパーメソッドを使ってRDocへのリンクを表示する。
開発に必要な要素技術や知識分野
  • Ruby一般の知識
  • HTML一般の知識
挑戦ポイント

特に挑戦ポイントはありません。RDocへのリンクを正しく生成する方法さえ見つけることができれば、あとは簡単です。

サンプルコードの実行結果をデータベース生成時に埋め込む機能

概要

現在、るりまプロジェクトではサンプルコードの埋め込みは手で実行した結果をコピー&ペーストしています。しかし、すべてのサンプルコードの実行結果が正しいことを確認できていないので、ときどき、プロジェクトのITS*3に「サンプルコードが動きません」や「サンプルコードが間違っています」のような報告があります。また、Rubyのバージョンアップによって挙動が変化したものへの追従も手動で行なっているため、完全に追従できているわけではありません。

人力ですべてのサンプルコードをRubyのバージョンごとに実行し結果をコピー&ペーストするのは、現実的ではありません。我々はプログラマなので、自動化できるところは自動化したいと考えています。

そこで、インターンシップの期間を用いて以下の機能を開発したいと考えています。

  • サンプルコード専用の記法を導入し、コードブロックを実行したり別ファイルに書かれたサンプルコードをインクルードしつつ実行し、結果を埋め込む機能。
  • 読む人の使っているRubyとサンプルコードを実行したRubyはバージョンが同じでもプラットフォームやパッチレベルが違う可能性があるのでそのサンプルコードを実行したときのruby -vも埋め込む機能。

この機能を開発することにより、以下のことを期待しています。

  • すべてのサンプルコードが自動的に実行されるため公開されているドキュメントに間違いが入りづらくなる。
  • サンプルコードの実行結果も自動で埋め込まれるのでドキュメントを書くコストが下がる。
  • Rubyの新しいバージョンがリリースされたときでもドキュメントを再生成するだけなのでメンテナンスコストが下がる。
開発の流れ(現時点での想定)
  1. チケットを読む。
  2. サンプルコード用の記法の仕様を検討する。
  3. 上で検討した結果を元にしてBitClustを変更する。
  4. 作成した記法をいくつかのドキュメントに導入して動作を確認する。
開発に必要な要素技術や知識分野
  • Ruby一般の知識
  • HTML一般の知識
挑戦ポイント

BitClustはほとんど外部ライブラリを使用せずに開発されているのでブラックボックスになっている部分がありません*4。そのため外部ライブラリをあまり使用せずにRubyを用いてアプリケーションを開発するときの参考になるでしょう。機能を実現するのに既存のツールを使用するか自前で実装するのか決める必要があります。

BitClustの既存の記法と衝突しないように設計する必要があります。また、その記法が本当にそれでいいのかコミュニティと合意を形成する必要があります。

まとめ

インターンシップで開発したいるりまをより便利にする機能を2つ挙げました。これ以外にも色々と開発したい機能はあるのですが、それについてはまた別の機会に書きます。この中に「挑戦してみたい!」と思える機能があれば、インターン募集ページから是非ともご応募下さい。

*1 milter managerを使うとRubyでmilterを開発することができます。

*2 るりまプロジェクトで使っているドキュメント生成や検索機能を提供するツール。

*3 Issue Tracking System

*4 ウェブアプリケーション部分の実装でRackを使っているくらいです。

2013-04-30

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|