おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

RPM で LSB 準拠プリンタドライバのパッケージを作ってみた (その2: 実践編)

えー、前回 (d:id:naruoga:20090706:1246858558) で長々と能書き&下準備を垂れましたので、次はいよいよ実践編。


とその前に書き忘れ。まあ大抵の人には今更のことですけど。

RPM 概略

RPM とは Redhat Package Manager の略だったんだけど、今は RPM Package Manager と名前が変わっております。

三つの材料、

  • 元々のプログラムをビルドするためのソース
  • それを特定環境のパッケージに併せるためのパッチ
  • それらのビルドやインストール、前処理、後処理を記述した SPEC ファイル

を用意して、rpmbuild というソフトで加工するとうりゃっと出来上がるわけです。
しかしオイラ的には、

  • SPEC ファイルが外部ファイルからのマクロ読みまくりなので環境依存しちゃってわけ分からん

つーところが何よりいやなのと、DebianDEB みたいに

  • 実態は単なる tarball なので解いて内容を確認するのが簡単
  • 依存関係のミス、設定ファイルの書き間違いなどをチェックする機構を有している
  • パッケージソースがそれで閉じているので誰が作っても同じパッケージになることが保証される

って筋がイイコを見てるともーヤんなるわけですよ。
つか SPEC ファイル最悪。説明なしでマクロとか使いすぎ*1

それでも LSB の標準は RPM であって DEB ではないのです。
なんで? というと「RPMDEBalien で可能だけど、逆は不可能だから」って理由らしい。トホホ。

まあ愚痴っていてもしょうがないので RPM を作ってみましょうそうしましょう。

パッケージの準備……をしようと思ったら。

今回は Postscript の PPD だけという一番単純な作りにしましょう。
そうすると PPD とそれ以外に必要なのは COPYING と README ぐらいでしょう。
これをどんな風にすべきか……が……いかなるドキュメントを読んでもさっぱりわかりません ;><
オイラの頭が悪いんでしょうか?
ということでまずは見捨てて SPECS を書くことにします。

SPECS 書き書き

もちろん前述のドキュメントには SPECS の書き方はばっちり書いてあるのですが、こんなのパクった方がはえーよ、ということでドキュメントにもパクリ元が提示してあるのでありがたーくいただいておくことにする。
Postscript 用の PPD のみで構成されているパッケージであればまぁどれでもよいでしょうから、openprinting-ppds-postscript-xxx.specs というファイルをなんか適当に選んで、

$ mv openprinting-ppds.postscript-xxx.specs ~/tmp/my-ps-ppds.specs

とかなんとかしてください。

まずはプロローグ

さて元ネタがあればわかりが早い。
まずはプロローグ。

Summary:        PPD files for hogehoge Postscript printers
Name:           hogehoge-ps-ppds
version:        20090626
Release:        1lsb3.2
License:        MIT
Group:          Applications/System
URL:            http://your.url/

ここは別に動き的にどーとかいう話じゃないので、自分のパッケージングルールにしたがって決めればいいと思います。
強いて言えば Group: の Applications/System はドライバパッケージの場合は決めうちです。
バージョンやリリースについてもやはりそれぞれの事情があると思いますが、対応 LSB バージョンはどこかに入れておいた方がよいと思います。私の例では Release に入れました (つかサンプルのパクリ)。

各種名称定義

お次は実際の処理のなかに出てくる文字列を定義します。

%define drivername hogehoge-ps
%define extraversion %nil
%define driverstr hogehoge-ps
%define supplierstr hogehoge
%define supplier hogehoge
%define ppdnickname , %{driverstr} %{version}%{extraversion} (%{supplierstr} LSB 3.2)

注意すべきなのは、ここで出てくる supplier という文字列が最終的に ppd がインストールされる場所 /opt//... になるので、あんまりヘンテコな名前にしたりするのは辞めた方が無難ですよということです。つかそんな名前空間でホントに大丈夫なんかいなというのは置いておきます*2

あと ppdnickname はまるぱくりで構わないと思います。DI-Package では PPD の *NickName をこの情報で上書きするようになっている。たとえば、

Original: 「Hogehoge PS B/W」

Modified 「Hogehoge PS B W, Hogehoge-PS 20090626 (Hogehoge LSB 3.2)」

のように。この *NickName は CUPS やデスクトップが提供する PPD 選択画面で用いられます。ので、ここでも LSB バージョンは明記するようにしましょう。

パッケージ情報

パッケージの依存関係やパッケージの構成要素などなどの情報を書きます。さっき挫折したパッケージのコンテンツの格納方式がわかるかも?

BuildRequires:  lsb-build-cc, lsb-build-c++, lsb-appchk
BuildRequires:  perl, gzip, cupsddk

Requires:       lsb >= 3.2

Source0:        hogehoge-ps-ppds-%{version}.tar.gz
Source1:        hogehoge-ps-ppds-%{version}.README_ja.htm
Source2:        hogehoge-ps-ppds-%{version}.COPYING

BuildRoot:      %_tmppath/%name-%version-%release-root
BuildArch:      noarch

今回は繰り返すように ppd のみなので BuildArch が noarch なのがミソ。もしバイナリの場合はそれなりにかかないとダメです。コイツについては後日談があるのでそれは後ほど。

問題は……ここで Source0 に指定されている PPD の tar.gz ファイルのファイル構成がまるっきりこれっぽっちも分からないこと。どのドキュメント見てもわかんない。えーん (T_T)
ということでこれはまあ後回しということにして。←いいのか?

Source1、Source2 は COPYING と README ですが、これは各ドライバで違うでしょう。がまあ例なのでこれはこれで。

Description

これはパッケージがどういうものかの説明をするものなのでちゃんと書きましょう。一行目は最初の Summary と併せておいた方がいいと思います。

%description

PPD files for hogehoge Postscript printers

This packages includes PPDs for hogehoge Postscript Printers/MFPs,
...
以下ビルドプロセス

ここは結果的にはサンプルからのコピペですみました*3

ミソは %install_into_opt で、これは下準備編で /etc/rpm/macros に記述したもの。これを書いておくとあら不思議、PPD やドキュメント類が先ほど定義した supplier にインストールされるというもの。Distribution Independent Package on LSB を作るときには必ず先頭に入れましょう。

パッケージ記述は次のようなセクションに分かれます。

セクション 意味・注釈
%prep ビルド前の下準備などなど。Source で指定されたソースを解いたり Patch を当てたり。ポイントは Source の指定を解決する %setup マクロ。こいつがすべて悪いのだ*4
%build ビルド。noarch な RPM の場合はビルドが発生しないのでからっぽ。
%install ビルドでできたオブジェクトをインストールする。今回のパッケージの場合にはコメントにも書いてあるとおり %adjust_ppds というマクロの呼び出しが必須。なおドキュメント系は思い切り手書きで処理しております。
%pre インストールの前処理の記述。ここでは /opt 以下に適切なディレクトリを掘るマクロ %create_opt_dirs を呼び出しているだけ。
%post インストール後の処理。ここでは少し分かりにくい処理をしているので後述します。表外で説明します。
%preun アンインストールの前処理。このパッケージではやることがないのでセクションごと存在しない。
%postun アンインストール後の処理。これも後述表外で。
%clean rpmbuild でビルド環境を綺麗にするときに。
%files パッケージにあるすべてのファイルのリストを記載する……ってことですが、サンプルからの丸コピで動いてますので多分それで大丈夫。

では各セクションごとに説明しましょう。
[%prep]

# Packaging settings
%install_into_opt


%prep
# remove old directory
rm -rf $RPM_BUILD_DIR/%{name}-%{version}%{extraversion}

mkdir $RPM_BUILD_DIR/%{name}-%{version}%{extraversion}
%setup -q -T -D -a 0 -n %{name}-%{version}%{extraversion}

%prep の前にある %install_into_opt というのは /etc/rpm/rpmmacros で定義されたマクロであり、一見ここで PPD の opt へのインストールを行うように見えますが、実際は後ほどのインストール処理でインストール先を /opt に向けるためのマクロです。

%prep の処理の rm と mkdir は特に問題ないが、%setup は特徴的なマクロなので紹介しておきます。
%setup は %prep でよく使われるマクロで、RPM 作成パッケージの一部として供給されています。以下 JF-Project のサイト http://www.linux.or.jp/JF/JFdocs/RPM-HOWTO-6.html より解説を引用します。

引数 意味
-n {name} 列挙された name に作成ディレクトリの名前をセットします。デフォルトは $NAME-VERSION です。他の可能性として $NAME、 ${NAME}${VERION}、メインの tar ファイルが用いているものです。 (これらの "$" 変数は spec ファイル中の本当の変数でない事に注意して下さい。これらは参考のためにここで使われたものです。実際には変数ではなくパッケージ中の本当の名前とバージョンを使う必要があります。)
-c tar ファイルを解凍する前に名付けられたディレクトリを作りそこに cd します。
-b # ディレクトリに cd する前に Source# を 解凍(untar)します。 (これは -c オプションと一緒に用いるのは意味がありません、しないで下さい。) これは複数のソースファイルがある時のみ役に立ちます。
-a # ディレクトリに cd した後に Source# を解凍(untar)します。
-T このオプションはソース解凍(untar)のデフォルト動作を無効にします。解凍(untar)されたメインのソースファイルを得るために -b 0 もしくは -a 0 を必要とします。2つめのソースがある時にこれを必要とします。
-D 解凍する前にディレクトリを消去しません。これは setup マクロが複数ある時のみ有用です。最初の setup マクロの後の setup マクロでのみ使うべきです。(決して最初の setup マクロで使わないで下さい。)
-q ソース解凍時の経過表示を無効にします。

ということで、この指定は

「%{name}-%{version}%{extraversion} に CD して
Source0 だけを untar して
残りは手つかずで
解凍前にディレクトリは削除せず
untar の経過表示は行わない」

という意味になります。

[%build]

%build
# Nothing to build

再三繰り返してきたとおり noarch の RPM ではビルド行程が存在しないので、単純にコメントでその旨を書いているだけです。

[%install]

%install
rm -rf %{buildroot}

# Make directories
install -d %{buildroot}%{_cupsppd}
install -d %{buildroot}%{_docdir}/ricoh-basic-ps-ppds

cp -r ppds/* %{buildroot}%{_cupsppd}/
chmod -R u+rwX,go+rX %{buildroot}%{_cupsppd}

# Rename PPDs appropriate to LSB agreements and compress the PPD files
%adjust_ppds

cat %{SOURCE1} > %{buildroot}%{_docdir}/ricoh-basic-ps-ppds/README_ja.htm
cat %{SOURCE2} > %{buildroot}%{_docdir}/ricoh-basic-ps-ppds/COPYING

見たとおりの意味であるので細かい説明は省略しますが、%adjust_ppds は上記のコメントに書いてあるように、LSB 準拠に PPD 名称をリネームして、gzip するためのマクロでです。ここで必ず呼ばなければなりません。
なお末尾の二行の cat は単にドキュメントを作成しているだけでです (リネームしてコピーなので単に cp でも良いはずです)。

[%pre, %post, %postun]

%pre
%create_opt_dirs


%post
%set_ppd_links
%update_ppds_fast
%restart_cups


%postun
%not_on_rpm_update
%remove_ppd_links
%restart_cups
%end_not_on_rpm_update

%pre では %create_opt_dir マクロで /opt の下にディレクトリを作成します。

%post では次の三つの処理を行います。

  1. まず %set_ppd_links というマクロで %{_cupsppd} フォルダにインストールされた PPD から /opt にリンクを張ります。
  2. それから、%update_ppds_fast で、すでにプリントキューに使用されている PPD を上書きした場合、それを入れ替える処理を行います。
  3. 最後に、%restart_cups で cups デーモンをリスタートします。

%postun では %post の逆とも言える処理を行います。ただし、%postun は RPM のアップデートのときにも呼ばれるセクションですので、アップデート時には不要な処理をバイパスする %not_on_rpm_update / %end_not_on_rpm_update で囲ってあります。

  1. %remove_ppd_links というマクロで %set_ppd_links で張られたリンクを切ります。
  2. %restart_cups で cups デーモンをリスタートします。

[%clean]

%clean
rm -rf %{buildroot}

名前の通り rpmbuild で clean を指定したときの処理です。

[%files]

%files
%defattr(-,root,root)
%if %{optinstall}
%{_prefix}
%else
%{_cupsppd}
%endif
%docdir %{_docdir}

RPM パッケージを構成するファイルです。noarch の (PPD のみの) DI-Package では上記 (サンプルどおり) をそのままマネすれば大丈夫です。

Changelog

言わずとしれた ChangeLog なのでまあ適切に書いてください。当たり前ですけどここのバージョン番号は %{version} で置換しちゃダメです。あと日付の書き方も間違えると (26th とかしたり、月と日を入れ替えたり) エラーで怒られるので気をつけましょう。

 %changelog

 * Fri Jul 26 2009 hogehoge PPD packager  2006626-1lsb3.2
 - Test release

Sources 再び

さてと SPECS は書けたっぽいので、今度こそ Sources に挑みましょう。
COPYING と README は単純にファイル名変えるだけなので、問題は PPD だ。

これを「PPD はこうやって配置するから、あなたは tar.gz をこう作って」ってドキュメントがどこにもねーのよ。あなた。いや私の目が節穴である可能性は否定しないけど。
それが素直にシェルスクリプトかなんかで書いてあればいいけど、わけの分からないマクロの山なので皆目わかりません。あーやだやだ。

ということで論理的アプローチをかなぐり捨てて試行錯誤だ。
まずは PPD そのままにするか、gz 圧縮をするかどうかですが、SPECS をぼーっと見てる限りでは gz 圧縮するのはやってくれるんじゃないかなーって気がしました。いや根拠レスです。
そうするとあとはディレクトリ構造ですが、

  • 掘らない
  • /opt 以下
  • /opt/ 以下

の三択かなと。で、わざわざ %install_into_opt なんてマクロ書いてくれてるぐらいだから*5 ディレクトリ掘らないで *.tar.gz 作ってみたさ。

さて実行! えい!
うむ。%build 直前まではうまく行く。しかし %install でコケる。
なんだかしかも %{buildroot} に x86_64 なんて文字があるじゃーないか。あれえっ? BuildArch 効いてない? じゃあ --target=noarch で。くそっ。だめだっ。なんでなんでなんで?

いろんな文献読み漁ったりぐぐったりすること2時間。
くたびれてエラーメッセージに戻ってきて、よくよく見ると……ん?
これ、%{buildroot} のパスに .../optppds/... 入れば万事解決じゃね?

ということで optppds/ 以下に PPD を配置する形で *.tar.gz を作ったら成功!
インストールもうまくいった!*6
alien による DEB パッケージの生成も大丈夫でした。

まとめ

苦労しましたが分かってしまえば簡単でした。
SPECS の書き方のなかで、OpenPrinting で定義したマクロについてはドキュメントがかなり丁寧に書いてあるので、noarch のパッケージの SPECS を書くのは特に難しくないと思います。

Sources の tarball はハマりました。%setup の説明ってどこかにちゃんとまとまってるんでしょうか ;>< 結局どのマクロがどういう動きをして最終的にファイルがどう配置されるのかとかそういうのが全然わからなくて(これはドキュメンテーションの問題も大きいと思う)そこがつらいなーと感じました。

ちょっと SPECS の %setup マクロについてはまた勉強しよ。
どうせ次には noarch じゃないパッケージを作るお稽古するんだし。

*1:RPM 大好きっ子な皆さんごめんなさい。

*2:ここちょっとだけ標準化で議論あったんですが、まあそんなにどうせベンダいないし、ということで落ち着いちゃいました。

*3:一部修正してたの忘れてました ^^;

*4:大げさ。

*5:後から考えると甘々だった。

*6:ただし、Fedora 11 の CUPS は 1.4 b3 なのですが、どうも /opt の下と /usr/share/ppds の下に同じ PPD があると二個列挙されてしまうらしくちょっと悲しいです。バグ? 仕様?