このシリーズの最初のパートでは、RPMの基本的な使用法について説明し、システムの検査方法とそこにインストールされているパッケージの変更方法に重点を置きました。この記事ではもう少し高度な領域まで深く検討し、RPM自体の核心部分、RPMの実際の作成まで踏み込みます。
自分でRPMを作成することが役立つ理由はある程度明らかですが、それは強調する価値があります。RPMによってシステムの高度に決定論的な展開とメンテナンスを実現できるということは自明ではないからです。もう少しわかりやすく言えば、RPMではシステムに何がインストールされているかを正確に知り、それと同様の他のシステムをすばやく複製することが非常に簡単にできます。たとえば、5台のサーバを管理していて、そのうち4台がWebサーバで、5台目のデータベースサーバと通信する場合を考えます。事業がうまくいって5台目のWebサーバを配備する必要が出てきます。これらのサーバで実行されているあらゆるソフトウェア(OS自体の一部、サードパーティ、または自作のいずれか)がRPMフォーマットである場合、新しいマシンをセットアップして他の4台のWebサーバの構成に厳密に一致させるのは非常に簡単です。これは、パッケージングがそれほど厳格ではない他のLinuxディストリビューションや他のUNIX環境とは大きく異なります。もちろん、ここで重要なのは訓練です。ソフトウェアをコンパイルしてRPMを使用せずにソースから直接インストールした場合は、ベースOSは簡単に復元できても、システム全体をオンラインにするのはそう簡単ではありません。
ソフトウェアの作成は、その性質からして、ソフトウェアの単なるインストールや削除よりも複雑です。そのため、この記事では、前の記事よりもやや高度な知識を持った読者を対象としています。特に、他者のパッケージを変更したり、最初から独自のパッケージを作成したりする必要のあるシステム管理者を想定しています。これは、その他のユーザが読んでも得るものがないという意味ではありませんが、ソースのコンパイルとその他の基本的な管理作業を熟知していることが前提となります。
パート1では強調しませんでしたが、実はRPMには2つの種類、ソースRPMとバイナリRPMがあります。ソースRPM(SRPM)の多くの特性は、バイナリRPMと同じです。名前、バージョン、およびリリースを持ち、内部にはファイルが格納されています。また、パート1で説明した一般的なコマンドラインツールとオプションのほとんどを使用してクエリーを行うことができます。一方、主な違いは、(名前が示唆するように)SRPMに、実際のコンパイル済みバイナリではなく、バイナリRPMを作成するために使用されるオリジナルのソースファイルが含まれることです。
多くの場合、ソフトウェアをダウンロードしようとすると、RPMと並んでSRPMを入手できることがわかります。接尾辞が.i386.rpmではなく.src.rpm(または、ある場合には.noarch.rpm)の場合、これらのSRPMは単なるソースコードのコンテナではありません。前述のように、SRPMに対して通常のバイナリRPMのようにクエリーを実行できますが、これは単一の一貫したコマンドによって直接構築することもできます。これは重要なコンセプトです。SRPMがconfigureとmake installを使用するパッケージを含むか、より複雑なコンパイル方法を使用するパッケージを含むかにかかわらず、SRPMはそれを抽象化します。実はこの抽象化こそ、この記事の主題です。
すべてのSRPMには、実際のソースファイルのほかに、スペックファイルが含まれています。このスペックファイルには、ソースコードをバイナリRPMへとコンパイルするために必要な情報のすべて、および名前、バージョン情報、説明など、生成されるRPMに関するその他のデータが格納されています。標準的なスペックファイルで明らかに最も複雑な部分は、ソースファイルのコンパイルに関する部分です。多くのオープンソースプロジェクトのコンパイルを行ったことのある人なら誰でも、多様なコンパイル方法があり、またほとんどのビルドのコンパイルおよびインストールの諸段階が幅広く成熟したものとなっていることがよくわかっています。スペックファイルが複雑になる場合があるのは、こうした理由からです。RPMにはソースをバイナリにする方式についてかなり具体的な情報が含まれますが、ビルドプロセスをこれに適合させるのは、スペックファイルの作成者です。
スペックファイルのサンプルを示す前に、ビルドの主要なセクションを検討しておきます。これらのセクションの一部は技術的にはオプションであり、あるステップ内で技術的には別のステップに属することを実行してしまうことが可能な場合もありますが、大部分のスペックファイルには各セクションがあり、通常のフローがあります。
ビルドの最初のステップは、prepステップと呼ばれています。prepセクションでは、ソースtarball(存在する場合)の復元と展開、そのtarballに含まれるディレクトリへの移動、およびRPMに含まれるパッチの適用(存在する場合)が行われます。手動でビルドする場合、これはtar zxfv foo-1.2.3.tar.gzコマンドに続けてcd foo-1.2.3コマンドを実行することに相当します。
第2のステップはbuildステップです。このステップでは、ソースパッケージ(prepステップで展開およびパッチ適用済み)がコンパイルされます。通常、これはほとんどのユーザになじみのある./configureおよびmakeステップに相当します。さらに、通常はこの時点でソフトウェアに付属しているテストがあれば実行し、ビルドの成功を保証します。
第3のステップはinstallステップです。予想されるとおり、これは、ソースでパッケージをコンパイルするときのmake installステップに相当しますが、非常に重要な例外が1つあります。このステップでは、ソフトウェアはビルドルートにインストールされる必要があります。簡単にいうと、ビルドルートはRPMをビルドする間に作成され、ソフトウェアがインストールされる一時的なサブディレクトリにすぎません。これはルートパーティションでのインストールとは対照的です。これは非常に重要なので繰り返す価値があります。installステップでは、通常の方法でソフトウェアをインストールする場合、ファイルは目的の場所へは置かれず、別個のディレクトリに置かれます。たとえば、make installによって通常、ファイルが/etc/sysconfig/network/に置かれるとすると、スペックファイルの内部では、そのファイルは$RPM_BUILD_ROOT/etc/sysconfig/network/に置かれます($RPM_BUILD_ROOTの詳細はあとで説明します。ここでは、シャドウツリーとして作成されるディレクトリと考えてください)。
installステップは、スペックファイルの作成者が明示的に制御できる最後のステップです。ビルドプロセスの残りの部分は、スペックファイルの他の部分のデータに対するRPMの動作となります。第4のステップでは、RPMによって依存性検出という作業が行われます。実質的に、RPMは$RPM_BUILD_ROOT内のあらゆるファイルを走査し、それぞれをさまざまな方法で検査して各ファイルが正しく機能するためにほかのものを必要とするかどうかを判定します。たとえば、RPMが一般的な実行可能バイナリを見つけた場合、RPMはそのバイナリが必要とする共有ライブラリを特定します。同様に、RPMが実行可能スクリプトを見つけた場合は、RPMは最初の行を調べて使用されているスクリプト言語の種類を判定します(たとえば、/bin/bashや/usr/bin/perlを必要とするスクリプトかもしれません)。また、RPMはビルドルートを走査するときに共有ライブラリを認識し、それらのライブラリを要求するのではなく、提供するものとして、生成されるRPMにフラグを立てます。これによって、ほかのパッケージの依存性が満たされる場合もあります。
第5のステップでは、RPMはビルドルートに移動し、ビルドしているバイナリRPM内部のファイルをすべて配置します。また、RPMはヘッダを構築して、生成されるバイナリRPMにすべてのメタデータ(名前、説明、依存性など)を含めます。さらに、(ビルド済みのSRPMのリビルドではなく)スペックファイルとソースからビルドする場合は、このビルドで生成されるものは、ビルドに必要なスペックファイル、ソース、およびパッチを含んだSRPMとなります。
rpmbuildの第2の使用法は、スペックファイルに対する直接の使用です。この場合は、SRPMからの場合よりも制御の幅が広がります。これは、スペックファイルを仕上げるときに呼び出されます(変更とビルドを繰り返すような場合)。スペックファイルとソースで最初に行えることは明らかです。バイナリRPMのビルドです。これは、rpmbuild -ba foo.specコマンドによって実行できます。--rebuildと同様、RPMはスペックファイルと依存性を確認し、次にそこに記述されているビルドプロセスを開始します。もう1つの一般的な使用法は、バイナリRPMではなく、SRPMのみを生成することです。これは、rpmbuild -bs foo.specコマンドによって実行できます。スペックファイルに従わずに、rpmbuildはSRPMを生成します。
以上では、ある詳細事項を意識的に無視してきました。すべてを機能させるには、実際にファイルをどこに配置する必要があるかということです。RPMのビルドにはスペックファイル、1つまたはそれ以上のソースファイル、および複数のパッチが必要なため、これは必ずしもすべてを1つのディレクトリに入れるという問題ではありません。実はRPMは設定可能なレイアウトを使用し、これはデフォルトでは/usr/src/redhat/をベースとします。
/usr/src/redhat/ /usr/src/redhat/SOURCES /usr/src/redhat/SRPMS /usr/src/redhat/RPMS /usr/src/redhat/RPMS/noarch /usr/src/redhat/RPMS/x86_64 /usr/src/redhat/BUILD /usr/src/redhat/SPECS
このレイアウトでは、スペックファイルはSPECS/ディレクトリに配置され、ソースtarballとパッチはSOURCES/に配置され、さらに(ビルドが成功したとすると)SRPMとRPMはSRPMS/およびRPMS/
パッケージビルディングの基本的なルールの1つは、決してrootでビルドを行わないということです。rootでビルドを行うことには、数々の危険が伴います。スペックファイルに欠陥があると、ビルドを実行しているシステムが完全に破壊される可能性もあります。では、/usr/src/redhat/はデフォルトインストールでrootの所有になっていますが、root以外のユーザでどうやって実際にビルドを行うのでしょうか。最も簡単な方法は、chown /usr/src/redhatを実行して、ビルドを行うユーザに所有者を変更することです。ここでは以上のアプローチを採用しますが、別のアプローチとして.rpmmacrosファイルによってRPMを設定し、システム上の任意の場所のツリーを使用することもできます。
Summary: A very simple package.
Name: simplest
Version: 1.0
Release: 1
License: GPL
Group: Development/Tools
URL: http://www.redhat.com/
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
BuildArch: noarch
%description
This is a very simple package to demonstrate an RPM build.
%prep
%build
%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/etc
touch $RPM_BUILD_ROOT/etc/empty-file
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
/etc/empty-file
例1. simplest.spec
これで前に説明したprep、build、およびinstallのセクションに到達しました。この例では、prepとbuildは完全に空白です。要するに、ビルドするソースtarballも適用するパッチもありません。installセクションもかなり簡単です。ここでは、まず$RPM_BUILD_ROOTを削除しています(以前のビルドの遺物を一掃してクリーンなビルドを保証)。次に、ビルドルートの下にetc/サブディレクトリを作成し、touchでそのディレクトリにファイルを作成しています。これだけです。最終結果は、ビルドルート内にディレクトリが1つ作成され、その中に空ファイルが1つ置かれた状態になります。cleanセクションは、ビルドが成功したあとにRPMがクリーンアップのために行う処理を表しています。これは、ほぼ常にビルドルートの削除です。
最後はfilesセクションですが、ここでは、ビルドルート内のファイルでRPMに含めるものを指定しています。filesセクションにはファイルのリストだけでなく、その所有者やモードも記述されています(rootとしてビルドを実行しない場合、rootが所有しているビルドルートでファイルは作成できないので、ここでは所有者を、ビルドを行うユーザではなくrootに指定しています)。また、このセクションでは、ファイルにコンフィギュレーションファイルのフラグが立てられることもあります(これはパッケージアップグレード中のRPMによるファイルの取り扱いに影響します)。
これをテストするには、例を/usr/src/redhat/SPECS/にコピーし、rpmbuild -bs simplest.specを実行してSRPMを作成するか、またはrpmbuild -ba simplest.specを実行してSRPMとRPMの両方を作成します。必要なら生成されるパッケージをインストールし、/etc/empty-fileがシステム上に生成されるのを確認することもできます。
CVSpsは特定のCVSリポジトリを分析し、チェックインをパッチセットに分割する便利なユーティリティで、他のバージョン管理システムとよく似ています。しかし、CVSの機能は限定されているので、SubversionやPerforceのようなバージョン管理システムのネイティブ機能と同じことを行うには、外部の「best guess」アプローチが必要です。CVSpsでは、これをかなりうまく実行できます。この例は、CVSpsのバージョン1.3.3をパッケージングしています。
例2「CVSpsのスペックファイル」は、スペックファイルを示しています。このファイルは単純さではsimplest.specとほとんど変わりませんが、いくつかの違いがあることがすぐにわかります。まず、このスペックファイルでは前の方に2つの新しいヘッダ、Source0とPatch0が存在します。名前が示唆するとおり、これらはそれぞれソースファイルとパッチファイルに対応しており、その番号から、特定のパッケージ内に複数のソースと複数のパッチを含めることができると推測されます。
Summary: A program to view patchsets of CVS checkins
Name: cvsps
Version: 1.3.3
Release: 1
URL: http://www.cobite.com/cvsps/
Source0: http://www.cobite.com/cvsps/%{name}-%{version}.tar.gz
Patch0: cvsps-1.3.1-fhs.patch
License: GPL
Group: Development/Tools
BuildRoot: %{_tmppath}/%{name}-root
%description
CVSps is a program for generating 'patchset' information from a CVS
repository. A patchset in this case is defined as a set of changes
made to a collection of files, and all committed at the same time
(using a single 'cvs commit' command). This information is valuable to
seeing the big picture of the evolution of a cvs project. While cvs
tracks revision information, it is often difficult to see what changes
were committed 'atomically' to the repository.
%prep
%setup
%patch0 -p1 -b .fhs
%build
make
%install
rm -Rf $RPM_BUILD_ROOT
make install prefix=$RPM_BUILD_ROOT/usr
%clean
rm -Rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
/usr/bin/*
/usr/share/man/*/*
%changelog
* Sat May 22 2004 Chip Turner - 1.3.3-1
- update to 1.3.3
* Sun Dec 16 2001 Chip Turner
- Initial build.
例2. CVSpsのスペックファイル
また、この例では、prepセクションが空白ではないことがわかります。そこには2つのステートメント(setupとpatch0)が記述されています。これらはいずれも、一見とするとセクション名のようですが、実はrpmbuildへのコマンドです。前述のように、prepセクションではソースの解凍とパッチの適用が行われます。ここで、patch0ステートメントがパッチを適用することは容易に推測できますが、setupがSource0を解凍することは明らかではありません。実際はこれが行われ、さらにtarballの内部に含まれているディレクトリに移動します。setupはrpmbuildの便宜的名称です。-qパラメータは、rpmbuildでtarballが展開される際にtarballの内容を表示しないように指定します。setupのその他のパラメータを、表1「setupのパラメータ」に示します。実際には-qと-nが最もよく使用されます。
パラメータ 説明
-n DIRNAME 変更される先のディレクトリ名(デフォルト: %{name}-%{version})
-q 内容を表示せずにsource0を展開する
-c 内容を表示せずにsource0を展開する
-T デフォルト動作をスキップする(-cを使用してuntar、usefilしない)
表1. setupのパラメータ
さしあたり、パッケージ内のパッチファイルの内容をスキップしますが、patch0ステートメントはパッチを適用します。-p1および-bパラメータは、コマンドラインユーティリティのパッチでコマンドライン上に見られるものと同じです。-p1はパッチでリストされるファイルから先行する1ディレクトリを消去し、-bはオリジナルをアップデートする前に、すべての変更されたファイルについてバックアップを保存します。この例では、バックアップには.lfsという接尾辞が付きます。
次はbuildセクションですが、これはきわめて単純明快です。CVSpsでは標準のMakefileが使用され、GNU Autoconf設定スクリプトは使用されないため、ここではmakeコマンドを実行します。また、CVSpsについてテストは含まれていないので、テストは実行しません(多くの場合、make testで呼び出されますが、特定のプログラムの実際のコンパイル手順と同じく、これはさまざまに変化します)。
インストールのフェーズも同じく単純です。単なるmake installではなく、prefixが指定されていることに注意してください。これは、インストールルーチンに対してファイルを置く場所を指定しています。この例ではprefix=PATHで指定していますが、多くの場合、PREFIX=PATHまたはその他のメカニズムが使用されます。どのソフトウェアのビルドについても、マニュアルを調べて正確な設定方法を確認してください。単純なソフトウェアや新しいソフトウェアでは、ルートファイルシステム以外の場所にはインストールできない場合もあります。これは最も面倒なケースで、多くの場合、ソフトウェアをビルドルートにインストールするにはパッチが必要になります。そういう例に遭遇して変更を加える場合は、パッチをオリジナルのプロジェクトに提出してください。ソフトウェアなど、ほかのユーザが利用できるものを追加することは(必ずしもRPMのビルドに関係しなくても)歓迎されます。
filesセクションは、例外的にワイルドカードを使用してファイルの位置を指定できます。パッケージのビルドによって数十、数百、または数千ものファイルが生成されるような場合は、これによって大幅に時間を節約できます。ファイルの最後にはchangelogセクションがあります。これはパッケージングの履歴を示しています(さらに、オリジナルバージョンが最新バージョンにアップデートされるまでに相当な怠慢があったことも示されています)。changelogのフォーマットはわかりやすいのであまり説明を要しません。changelogは、特にRPMをほかのユーザと共有するときに計り知れない重要性を持ちます。
今度は、前に無視していたパッチファイルを取り上げます。まずパッチの名前、cvsps-1.3.1-fhs.patchから始めます。パッチファイルのバージョンが1.3.1でパッケージのバージョンが1.3.3というのは初めは奇妙に思われるかもしれませんが、これは実はかなり一般的な表記です。新しいパッチを作成するときに、ソースファイルのバージョンをパッチ名に含めるのは便利です。これによって、パッチが作成された時期、およびどのソースツリーに対して作成されたかがわかるからです。時間が経つとバージョンは増加しますが、クリーンに適用するためにパッチファイルを変更する必要がないかぎり、パッチのバージョンを変更する必要はなくなります。パッチは変更を必要としなくなるため、1.3.1という記載のままとなります。バージョンのあとのfhsは、パッチに関する簡単な説明です。この例では、fhsはFilesystem Hierarchy Standardを意味します。これは各種のUNIX、特に多くの一般的なLinuxディストリビューションで採用されている標準規格です。
パッチ自体は小さなもので、Makefileという1つのファイルを変更するだけです。例3「CVSpsのパッチの例」に挙げられているパッチを調べると、若干の変更が加えられていることがわかります。実際上、このパッチはmanページを/usr/man/ではなく/usr/share/man/にインストールするようにMakefileに指示しています。これはFHSに準拠したもので、Fedora CoreとRed Hat Enterprise Linuxが両方ともFHS準拠のディストリビューションであるために必要となります。
--- cvsps-1.3.1/Makefile.lfs Thu Jun 27 11:02:46 2002 +++ cvsps-1.3.1/Makefile Thu Jun 27 11:03:02 2002 @@ -15,9 +15,9 @@ install: [ -d $(prefix)/bin ] || mkdir -p $(prefix)/bin - [ -d $(prefix)/man/man1 ] || mkdir -p $(prefix)/man/man1 + [ -d $(prefix)/share/man/man1 ] || mkdir -p $(prefix)/share/man/man1 install cvsps $(prefix)/bin - install -m 644 cvsps.1 $(prefix)/man/man1 + install -m 644 cvsps.1 $(prefix)/share/man/man1 clean: rm -f cvsps *.o cbtcommon/*.o core例3. CVSpsのパッチの例
パッチの適用方法とその結果を確認するのは容易ですが、パッチの作成には少々注意が必要です。そしてパッケージ内のパッチの数が増加すると、ますます注意が必要になります。しかし、RPMには、この作業をかなり簡単にするgendiffというユーティリティが含まれています。gendiffを使用するには、もとのtarballを任意のディレクトリに抽出します。この展開済みのtarballを調べて、編集するファイルをそれぞれ別の名前でコピーし、各ファイルに共通の接尾辞を付けます(この例の.fhsなど)。オリジナルのファイルを必要なだけ編集して、tarballを抽出した前のディレクトリに移動します。次にディレクトリ(cvsps-1.3.3など)と共通の接尾辞(.fhsなど)を渡してgendiffを実行します。gendiffの出力はunified diff形式で、パッチプログラムで直接使用する場合にも、またスペックファイルで使用する場合にも適しています。このdiffをビルドルートのSOURCES/ディレクトリに保存し、スペックファイル内でそれを参照します。diffを作成する方法はほかにも数多くあります。たとえば、変更を加える前にツリー全体をコピーし、次にdiff -Naurを使用してツリー全体の差分をとることもできます。また、各ファイルの差分を個別にとることもできます。しかし、gendiffの利点は、ユーザが指定していないファイルは対象に含まれないこと、および単一または複数のファイルを変更することが容易に可能になることにあります。
%_signature gpg %_gpg_name email@example.com
ここで、email@example.comはGPGキーを作成するときに使用した電子メールアドレスです(gpg --list-keysでも確認可能)。次にrpm --resign /path/to/rpms/を実行して、ディレクトリ内の1つまたはそれ以上のパッケージに署名します(バイナリとソースの両方のRPMに署名可能)。パッケージに使用されている署名を確認するには、当該のRPMでrpm -Kvを実行します。DSA署名を参照している行には、パッケージの署名に使用されているパブリックキーに相当する8桁の16進数文字列があります。
.rpmmacrosについて説明すると、前述のとおり、/usr/src/redhat/からビルド可能ですが、これを上書きできます。これを/home/username/rpm/に変更するには、次の行を.rpmmacrosファイルに追加します。
%_topdir /home/username/rpm
これによって、RPMでは、ビルドルートの上位ディレクトリが/usr/src/redhat/ではなく、/home/username/rpm/になります。このディレクトリの下に、/usr/src/redhat/の下にあったサブディレクトリを作成します。RPMは多くの場合、それらのサブディレクトリの存在を前提しているので、存在しなければ障害が発生することがあります。
1本の記事だけでRPMパッケージのビルドのすべてを説明することは不可能です。そして、この記事の目的はそこにはありません。この記事の目的は、RPMがパッケージをビルドする際の動作を理解し、単純で拡張可能な(そして最も重要なことに)理解しやすい2つの例について習熟するために、十分な情報を提供することです。rpmbuildの動作の基礎を理解すれば、複雑なRPMもそれほど不可解なものではなくなります。しかし、ソフトウェアの多様性からくる問題に終わりはなく、新しいコードのパッケージングはほとんど間違いなく新しい難問を生み出します。この記事によって理解を深めて訓練を積んでいけば、しだいにそうした複雑さや難問は減少し、数行の単純なスクリプトから企業のWebサイト全体まで、あらゆるものをパッケージングできるようになります。そして忘れないでください。オープンソースとまさに同じように、借用の術を心得ることが大切です。できるだけ多くのスペックファイルを読み、うまく機能するものとしないものを確かめてください。RPMのビルドについては、基本的な理論を学んだら、あとは芸術と同じです。スキルは必ず磨くことができます。
Chip TurnerはRed Hatに3年間勤務しており、Red Hat Networkのチーフ設計者です。また、彼はFedora CoreとRed Hat Enterprise Linux用にperlパッケージ、perlの全モジュール、およびspamassassinを保守し、RPMのRPM2 perlバインディングやその 他のいくつかのCPAN perlモジュールを作成しています。余暇には愛犬とたわむれ、雑談を楽しんでいます。