もりきゅうです。 本稿では ruby-rpm を解説します。 ruby-rpm は Ruby で RPM を扱うための拡張ライブラリです。 RPM については RPM HOWTO や Maximum RPM をご参照ください。
ruby-rpm を用いると、rpm コマンドを経由せずに Ruby から RPM DB を扱え、問い合わせ・インストール・アップグレード・削除の処理を実行できます。1
私が Momonga Project に誘われて ruby-rpm をいじるようになったのは、つい最近のことです。 RPM についてもあまり理解していなかったので、ruby-rpm と一緒に調べていくことにしました。 本稿はその調査記録として ruby-rpm の簡単な使い方を紹介します。2
RPM は Red Hat によって開発が進められています。RPM の機能は C ライブラリとして提供されており、これを用いた Python による拡張ライブラリ (rpm-python) が Red Hat によって提供されています (rpm-python は RPM のソースツリーに含まれています)。
拡張ライブラリという点で、ruby-rpm は rpm-python の Ruby 版と考えてよいのですが、仕様を見る限りでは特に rpm-python を参考にして開発されているわけでもないようです。
ruby-rpm はいくぶん古い API を元にしているので、最近の RPM を扱うために少々いびつな修正が行われています。 また、独自の仕様がいくつか見られます。これはおそらく momonga からの要請に沿ったものでしょう。 このような理由から、その実装は少々見通しが悪くなっています。改善の余地は大いにあります。
Momonga Project では Ruby を積極的に利用していますが、特にパッケージ管理ツールである OmoiKondara, mph-get は Ruby を用いた momonga 独自のツールです。そして、これらを支えているのが ruby-rpm です (でした)。 残念ながら、現在の OmoiKondara, mph-get は直接 rpm, rpmbuild コマンドを呼び出しており、ruby-rpm をあまり利用していません。 それは、ruby-rpm にいくつかバグがあった (また、RPM のバージョンを上げたときに十分対応できていなかった) ことが原因ではないかと考えられます。
ほとんどの場合、直接コマンドを呼び出しても十分な結果を得られます。しかし、ディストリビューションに含まれる全パッケージの依存関係を元にするようなツールを書くとき、コマンド呼び出しでは効率が悪かったり、きれいに実装できないところが出てくると思います (その前に、そのような問題が出てくるようなアイデアが必要なわけですけど)。 そこで ruby-rpm の出番というわけです。
どのような拡張ライブラリでも同じことが言えますが、ruby-rpm の開発を通して RPM の実装 (ソース) に対する理解を深めることができます。RPM は今後も多くのディストリビューションで使われていくパッケージングシステムであると思います。Ruby を通して RPM の理解を深めるのもまた一興ではないでしょうか。興味のある方は是非 ruby-rpm の開発にご協力ください。そして Momonga Project へご参加ください。
ホームページを確認すると、MURATA さんによって公開されている ruby-rpm の最終リリースは ruby-rpm-1.1.1 のようです。その README:
今となっては少々古い環境であることは否めません。ここでは momonga で配布されているより新しい ruby-rpm を試してみたいと思います。
momonga は svn で管理されています。現在の trunk/pkgs/ruby-rpm/ に置かれているのは次のファイルです。3
momonga ではパッケージを作るときに、基礎となるソースはそのまま置き、そこにいくつかのパッチをあてて momonga としてリリースしています。 ここで扱う ruby-rpm では ruby-rpm-1.2.0.tar.bz2 に ruby-rpm-1.2.0.diff というパッチをあてて作ります。
このようなパッケージを作るときに必要なソースやパッチのあて方は、RPM で扱う際には spec ファイルという設定ファイルに記述しておき、rpmbuild というコマンドにこの spec ファイルを渡します。
さらに momonga では OmoiKondara が rpmbuild コマンドを呼び出してくれます。
というわけで、実際には手間要らずなのでした。 ただデバッグのときには手動で rpmbuild を使うことがあります。
RPM の実験を Windows 上で行いたい方も居られるでしょう (私もそのひとりです)。
まず、Cygwin の RPM を導入する手があります。しかし今のところ Cygwin は RPM のヘッダとライブラリを配布に含めていません。ですから ruby-rpm を作ることができません。
それでも Cygwin で ruby-rpm を使おうとするなら RPM をソースからビルドする必要があります。私の試した rpm-4.1 では Cygwin でもビルドできました。そして ruby-rpm も (少し Makefile をいじる必要がありましたが) ビルドできました。
Cygwin を使わない方法として X on Win パッケージがあります。 Cygwin で RPM を使う方法を探していると、HOLON Linux が提供している X on Win パッケージを見つけることができました。4 X on Win のブートパッケージを使えば Windows 上で一から RPM 環境を作ることができるようです。興味のある方はお試しください。
ruby-rpm はまだ十分にテストできていません (テストケースを書きましょう!)。そのため、何らかの拍子に RPM DB が壊れてしまうことがあります。 実行する前に、RPM DB のバックアップをとる手順と、壊れたときのリカバリの手順を確認しておきます。
/var/lib/rpm を tar などで固めれば ok です。
壊れると
このようなエラーが延々と出ます。 こうなってしまったら RPM DB を再構築するために
を実行してください。/var/lib/rpm/Packages さえあれば再構築できるようです。
RPM DB の位置は RPM の root という設定項目で決まります。デフォルトでは /var/lib/rpm に作られます (この場合 root=/ です。どこに root を設定したとしても必ず #{root}/var/lib/rpm ディレクトリは作られます)。 root を明示的に指定するには –root オプションを与えます。 例えば
とすれば ~/var/lib/rpm/Packages という DB (Berkeley DB) が作られます。 すでに DB が存在するときは rpm –initdb は何も行いません。 でも、からっぽの DB を用意してもあまり使い道がないですね。
ruby-rpm 上でも rpm –initdb, rpm –rebuilddb と同様の操作を行えます (あまり使う機会はないかもしれませんけど)。
ruby-rpm を使って RPM DB に問い合わせる方法、パッケージをインストール・アップグレード・削除する方法を見ていきます。 対応する rpm コマンドも書いておきます (オプション・フラグの類は今回扱いません)。
インストール済みのパッケージについて問い合わせるには RPM::DB オブジェクトを用います。
RPM::DB.open で RPM DB を開き、db.each_match(RPM::TAG_NAME, “ruby-rpm”) で ruby-rpm という名前を持つパッケージを問い合わせています。 同じ名前であってもバージョンが異なるパッケージをインストールできますから、each_match の結果は複数になることもあります。
RPM::DBI_* 系のタグは大きなくくりになりますが、RPM::TAG_NAME と RPM::DBI_LABEL で違いはないと思われます。
db.each, db.each_match は内部で db.init_iterator, mi.next_iterator を呼び出しており、外部イテレータから内部イテレータへ変換しています。
db.each_match を Ruby で実装すれば次のようになります。
引数の形式として正規表現やグロブを与えることもできます。 現在の ruby-rpm の仕様では、引数の形式を指定するために mi.set_iterator_re を用いなければならず、結果的に外部イテレータの形で記述する必要があります。 rpm コマンドではグロブ形式で引数を与えることができます。 これを ruby-rpm で表現するためには、次のように書きます。
mi.set_iterator_re の第二引数 mode=RPM::MIRE_GLOB が引数の形式を表します。RPM::MIRE_GLOB のほかに RPM::MIRE_DEFAULT, RPM::MIRE_STRCMP, RPM::MIRE_REGEX を与えることができます。
rpm -q (–query) のオプションは多彩です。 これらの多くは popt ライブラリによる alias として実現されています。 alias の設定は rpm-4.3.2 ならば /usr/lib/rpm/rpmpopt-4.3.2 にあります。また、ユーザごとに ~/.popt に記述することができます。
この設定を見ると
は
と同等であることがわかります。 –qf (–queryformat) は ruby-rpm では RPM::Package#sprintf として指定できます。
ファイル名からパッケージを得るには -qf を用います。これを ruby-rpm で実現するためには、タグとして RPM::TAG_BASENAMES を与えます。
RPM DB を更新する処理 (トランザクションを扱う処理) では、RPM::DB.open の第 1 引数として true を指定する必要があります。 また、rpm コマンドと同様、RPM DB を更新する場合は sudo が必要になります。
パッケージをアップグレードするには次のようにします。
ちょっと長くて読みにくいですが、長くなっている commit {} のブロックを外してみてください。
ts.upgrade(pkg, pkg_key) でアップグレードトランザクションを追加しています。
ts.check で依存関係を確認します。
ts.order で処理順序を決定します。
ts.commit でトランザクションを実行します。
RPM の API ではトランザクションセットにトランザクションを追加していくと考えます。 db.transaction が返す ts は Transaction Set なのです。 でもロールバックは ts 単位なんですよね……謎。 ちょっとこの辺り ruby-rpm の名前付け (というより RPM の API 名) は混乱しています。
commit にブロックを渡すと、コミット処理の進行状況に応じてブロックが評価されます (このようなブロックの使い方をコールバック (callback) といいます)。 ブロックを渡さないとデフォルトのコールバックを実行します (キャラクタベースのプログレスバーが表示されます)。
ts.commit を呼ばなかったときは ts.transaction {} を抜けるときに内部で ts.commit が呼ばれます。 この際、ブロック呼び出しが妙なことになって、ts.transaction に渡したブロックが ts.commit を読んだ際にも呼ばれてしまいます (これはおそらく Ruby の不具合だと思います5)。
パッケージをインストールするときは、アップグレードの例の ts.upgrade を ts.install に置き換えればいいです。
パッケージを削除するには次のようにします。
ts.check の結果、依存関係に問題があれば ps (Problem Set) は RPM::Require オブジェクトを要素とする配列になります。問題がなければ ps は nil になります。
削除のトランザクションでは (インストールやアップグレードとは違って) 依存関係に問題があっても ts.commit は abort しないことに注意してください。 明示的に ts.abort しなければ、依存関係に問題があっても削除のトランザクションは実行され、あろうことか成功してしまいます (そしておそらく RPM DB は panic に陥り、–rebuilddb のお世話になります)。
[1] Red Hat RPM Guide Eric Foster-Johnson Published by Wiley Publishing, Inc. Copyright (c) 2003 by Red Hat, Inc.
もりきゅうは異業種社長 4 名 + 主婦 2 名 + 私という妙なパーティで運営している会社ミッタシステムのプログラマです。
著者の連絡先は moriq@moriq.com です。
本稿を書き始める前には、これらの処理は当然できるものと考えていました。しかし、テストしてみると不具合が見つかりました。幸いなことに直し方が判ったので、パッチを投げつつその成果を元に執筆することになりました。これぞ執筆駆動開発! ↩
実は、ruby-rpm の開発過程も書くつもりでした。しかし、ruby-rpm が何なのか事前に説明しておかないと話を始められないのでした。というわけで、このような紹介記事になりました。 ↩
現在パッチは増えています。 ↩
http://www.mars.dti.ne.jp/~sohda/cygwin/holon.html に詳しい。 ↩
[[ruby-dev:24252]] で報告済み ↩