はじめに
みなさんはspecファイル書いたことありますか?
specファイルを雰囲気フワフワな状態で書いていたエンジニア(私)が、
これを機にちゃんとまとめようと思った備忘録的なそれです。
specファイルってなんぞ
rpmパッケージを作る時に必要になるレシピ的なもの
rpmbuildコマンドを実行するときspecファイルに沿ってrpmパッケージを作成して行きます。
今回rpmbuildコマンドを実行した時どのような流れでspecファイルが利用されていくのかに着目していきたいと思います。
おことわり
rpmbuildコマンドは実行時$HOME/rpmbuild/ディレクトリをトップディレクトリとして実行されます。
任意のディレクトリでrpmbuildコマンドを実行した場合は--defineオプションを用いて_topdirの定義を一時的に変更する必要があります。
つまり$HOME/myrpm配下でrpmbuildコマンドを実行しようとした場合、rpmbuild --define="_topdir $HOME/myrpm"となります。
この記事ではspecファイルに注目するため、このようなrpmbuildコマンドの細かい部分はそれとなーく流します。
ディレクトリ構成
rpmbuildコマンドを実行した時に作成されるディレクトリ構成はこちら
_topdir
├── BUILD
├── BUILDROOT
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS
それぞれのディレクトリの用途は以下の通りです。
| ディレクトリ名 | 用途 | 作成されるタイミング |
|---|---|---|
| BUILD | rpmパッケージを作成するときに使用する作業ディレクトリ |
rpmbuildコマンド実行時に作成される |
| BUILDROOT | アプリケーションを仮想インストールする際にルートになるディレクトリ |
rpmbuildコマンド実行時に作成される |
| RPMS | 完成したrpmパッケージが置かれるディレクトリ |
rpmbuildコマンド実行時に作成される |
| SRPMS | 完成したsrpmファイルが置かれるディレクトリ |
rpmbuildコマンド実行時に作成される |
| SOURCES | rpmパッケージに含めるソースコードを置くディレクトリ | 自分で作成する |
| SPECS | rpmパッケージ作成に用いるspecファイルを置くディレクトリ | 自分で作成する |
実際にrpmファイルを作成していくときは、SPECSディレクトリにspecファイルを置き、SOURCESディレクトリにソースコードを置き、
rpmbuildコマンドを実行し、完成したrpmパッケージをRPMSディレクトリから取る形になると思います。
specファイルの構成
specファイルは以下のようないくつかの章が合わさって構成されます。
- 基本情報
- スクリプト部
- prepセクション
- buildセクション
- installセクション
- checkセクション
- cleanセクション
- ファイルリスト部
- 更新履歴
rpmbuildコマンドを実行したとき主に利用されるのがスクリプト部だと思います。
実際に私がrpmパッケージを作成したときもスクリプト部をいじっていた時間がほとんどでした。
各章・各セクションをちゃんとまとめたいと思ったのがこの記事を書くに至った経緯でもあります。
マクロの定義
specファイル内では自分でマクロを定義することができます。
頻繁に使用する記述をマクロで定義しておくと、いろいろと便利になります。
%define version 1.0
例えばこのようなマクロを定義した場合、specファイル内で%{version}と書くと1.0に置換されます。
また、標準で定義されているマクロもあります。先程述べた_topdirも標準で定義されているマクロのひとつです。
標準で定義されているマクロはrpmbuild --showrcコマンドで確認できます。めちゃめちゃあるので全部把握するのは難しいかも..
基本情報
この章にはrpmパッケージの基本情報を記述します。
これらはrpm -qiなどでパッケージ情報を呼び出す際に表示されるものになります。
基本情報として利用できるタグの一覧がこちら。
| タグ | 説明 | 必須 |
|---|---|---|
| Summary | パッケージの簡単な説明 | ○ |
| Name | パッケージ名。ここで定義した名前は以降%{name}というマクロで利用出来ます。 |
○ |
| Version | パッケージのバージョン。ここで定義した値は以降%{version}というマクロで利用出来ます。 |
○ |
| Release | パッケージのリリース番号。ここで定義した値は以降%{release}というマクロで利用できます。 |
○ |
| License | アプリケーションのライセンス | ○ |
| Group | アプリケーションの種類。CentOS7.5では/usr/share/doc/rpm-4.11.3/GROUPS内に列挙されています。 |
|
| Packager | パッケージメンテナの名前。複数いる場合はカンマ区切り | |
| Url | アプリケーションの情報を提供しているURL |
また、パッケージ作成に必要なソースやパッチもここで列挙します。
| タグ | 説明 | 必須 |
|---|---|---|
| Source0 | パッケージの作成に必要なソースファイル名を指定します。指定された名前のソースファイルをSOURCESディレクトリからさがします。ここで指定したファイルは以降%{SOURCE0}というマクロで利用出来ます。 |
○ |
| Source1... | 必要なソースファイルが2つ以上ある場合はSource1,Source2..と連番で列挙していきます。ここで指定したファイルは以降%{SOURCE1}や%{SOURCE2}のようなマクロで利用できます。 |
|
| Patch0... | パッチファイルを指定します。2つ以上ある場合はPatch1,Patch2..と連番で列挙していきます。 | |
| BuildRoot | パッケージ作成時に仮想インストールされるディレクトリを指定します。ここで定義した値は以降%{buildroot}というマクロで利用できます。 |
他のパッケージとの依存情報も列挙します。
| タグ | 説明 | 必須 |
|---|---|---|
| Requires | 作成したrpmパッケージが動作するのに必要なパッケージ名を列挙します。yumコマンド等でインストールする時、ここで列挙されているパッケージも同時にインストールされます。 |
|
| BuildRequires | パッケージの作成に必要なパッケージ名を列挙します。rpmbuildコマンドを実行するときに列挙されているパッケージが足りてないとエラーになります。 |
|
| Conflicts | 共存できないパッケージ名を列挙します。 | |
| BuildConflicts | パッケージ作成時にインストールしておけないパッケージ名を列挙します。 | |
| Obsoletes | パッケージをインストールする際に、アンインストールするパッケージ名を列挙します。 |
最後にdescriptionタグでパッケージの詳しい解説を記述します。
%description
This Application is
very very happy Application
長い!そして多い!
こんな量の情報を一つのファイルに書くのでそれはそれは巨大なファイルになりますよ。
そしてこれはまだまだ序の口。次は実際にパッケージのインストールなどを行うスクリプト部です。
スクリプト部
実際にrpmパッケージを作成していくスクリプトを書きます。
スクリプト部は以下のフォーマットで記述します。
%セクション名
シェルスクリプト
次のセクション名が現れるまでのシェルスクリプトを順番に実行します。
rpmbuildコマンド実行時に-vオプションをつけるとわかりますが、呼び出されるシェルは/bin/sh -eとして呼び出されます。
私が試しているCentOS7.5の環境ではbashにリンクされているので、specファイルのシェルもbashの構文に則っている必要があります。
まぁspecファイルの中であんまりごちゃごちゃするものでも無いと思うので気にするほどじゃないかもしれませんが...
セクション一覧がこちら
| セクション | 説明 |
|---|---|
| prep | ソースをビルドする前処理 |
| build | ソースのビルド処理 |
| install | ソースをインストールする処理 |
| check | ビルド結果を検証する処理 |
| clean | パッケージ完成後の後処理 |
| pre | RPMパッケージをインストールする時、パッケージ展開前に行う処理 |
| post | RPMパッケージをインストールする時、パッケージ展開後に行う処理 |
| preun | RRMパッケージをアンインストールする時、パッケージ削除前に行う処理 |
| postun | RPMパッケージをアンインストールする時、パッケージ削除後に行う処理 |
| triggerin | あるパッケージがインストールされていた、もしくはされたときに行う処理 |
| triggerun | あるパッケージの削除前に行う処理 |
| triggerpostun | あるパッケージの削除後に行う処理 |
| verifyscript | RPMパッケージを検証するときに追加で行う処理 |
この中から必要なセクションをspecファイルに記述していきます。すべて記述しなきゃいけないわけではありません。
なんなら全部なくても大丈夫です。何も実行されずrpmも作成されませんが...
最低限prep,build,installはないとrpmパッケージは作成されません。check,cleanもあるとなおいいでしょう。
その他のセクションは必要に応じて利用してください。
rpmbuildコマンドはprep→build→install→checkの順に実行しrpmパッケージを作成します。
rpmパッケージの作成が完了したらcleanセクションを実行します。
prepセクション
ソースのビルドをする前処理を行います。
以前rpmbuildコマンドを実行したゴミ等が残っている可能性があるため、
下記のように最初にBuildRootのクリーンを行うことが一般的です。
%prep
rm -rf %{buildroot}
prepセクションでは%setupマクロを使ってソースコードの展開を行ったり、
%patchマクロを使ってソースコードにパッチをあてることが可能です。
それぞれのマクロの挙動は下記の通りです。
%setupマクロ
主にソースコードの展開をおこないます。
Source0にhogehoge.tar.gzのようなファイルが指定されていることが想定されています。
実際の動きとしては下記の通り
-
%{name}-%{version}で指定されているディレクトリを削除します。 -
Source0に指定されているファイルをgzipで解凍し、tarで展開します。 -
%{name}-%{version}で指定されているディレクトリにcdで移動する。 -
chmodでパーミッションを変更する
chmodしてるのは知らなかった...
rpmbuildコマンドは実行するときに一時ファイルにシェルスクリプトを生成し、それを/bin/shに読ませる形で実行しているので、
実際に生成された一時ファイルを見ることで何をやっているかがわかります。%setupマクロが実際に行っている処理は以下のようになっていました。百聞は一見にしかず
cd '/home/shun/rpmbuild/BUILD'
rm -rf 'sample-script-1.0'
/usr/bin/gzip -dc '/home/shun/rpmbuild/SOURCES/sample-script-1.0.tar.gz' | /usr/bin/tar -xvvf -
STATUS=$?
if [ $STATUS -ne 0 ]; then
exit $STATUS
fi
cd 'sample-script-1.0'
/usr/bin/chmod -Rf a+rX,u+w,g-w,o-w .
GroupやOtherに書き込み権限があった場合はなくすようにしていたり、chmodで不要な権限は外しているんですねー
これは%{name}がsample-script、%{version}が1.0だったのでsample-script-1.0というディレクトリを対象にしています。
対象にするディレクトリを変更したり%setupマクロにはさまざまなオプションがあるのですがそれはまた別の機会に...
%patchマクロ
主に%setupマクロで展開したソースにパッチをあてます。
特にオプションなく%patchマクロを実行した場合patch -p0が実行されます。しかし、git diffなどでパッチファイルを作ると-p1オプションをつけるのが一般的です。
その場合は%patchマクロに-p1というふうにオプションを付けてあげれば実現可能です。
またパッチファイルが2つ以上設定されている場合は%patch0や、%patch1とすることで複数のパッチをあてることができます。
例えば以下のようになると思います。
%patch0 -p1
%patch1 -p1
この場合patchマクロが実際に行っている処理は以下のようになっていました。
echo "Patch #0 (sample-patch01.patch):"
/usr/bin/cat /home/shun/rpmbuild/SOURCES/sample-patch01.patch | /usr/bin/patch -p1 --fuzz=0
echo "Patch #1 (sample-patch02.patch):"
/usr/bin/cat /home/shun/rpmbuild/SOURCES/sample-patch02.patch | /usr/bin/patch -p1 --fuzz=0
buildセクション
主にパッケージをビルドする処理を書きます。
%setupマクロで展開したディレクトリにcdしてからスクリプトは実行されます。
Makefileにビルドするルールを記述しmakeのみで終わらせるのが一般的?
個人的にはそのほうがspecファイルがスッキリするのでお気に入りです。
またautotoolsを用いて開発されたソースなどで、configureスクリプトが用意されている場合%configureマクロが利用できます。
私自身がautotoolsを使ったことが無いので今回は割愛... autotools勉強して出直してきます。
installセクション
パッケージを仮想インストールする処理を書きます。
BUILDディレクトリでビルドしたアプリケーションをBUILDROOTディレクトリをルートディレクトリとし、仮想インストールします。
%buildセクション同様%setupマクロで展開したディレクトリにcdしてからスクリプトは実行されます。
仮想インストールなので%{buildroot}以下にインストールする必要があるので少し工夫が必要かもしれません。
私の場合はMakefile内でDESTDIRというような値のない変数を定義し、specファイル内で利用する時にmake install DESTDIR=%{buildroot}としたりします。
以下Makefileの例になります。
DESTDIR = #値は空のまま
.PHONY: install
install:
install -d -m 0755 ${DESTDIR}/usr/bin
install -m 0755 sample-script ${DESTDIR}/usr/bin/
specファイルの中身は以下のようになります。
%install
make install DESTDIR=%{buildroot}
こうすることでrpmbuildコマンド実行時には%{buildroot}配下にディレクトリやスクリプトがインストールされます。
さらに、rpmコマンドなどで実際にインストールする際には%{buildroot}の値が定義されないので、通常のルートディレクトリ配下にインストールされるようになります。
これはあくまで一例にすぎないので、正しくインストールができさえすれば何でも大丈夫です。
checkセクション
パッケージのビルドが正常に完了したかチェックする処理を書きます。
アプリケーションによってテストスクリプトなどがある場合はここで実行しましょう。
cleanセクション
rpmパッケージが完成した後の後処理を行う処理を書きます。
下記のようにBuildRootの削除などを行いましょう。
%clean
rm -rf %{buildroot}
ファイルリスト部
rpmパッケージに含まれるファイル名を列挙します。
%installまでスクリプトを実行した段階で列挙されたファイルが実際にインストールされている必要があります。
列挙されているファイルがインストールされていなかったり、逆に列挙されていないファイルがインストールされていたりした場合rpmbuildコマンドは失敗します。
%attr
%attrを用いるとファイルのパーミッションやuserID, groupIDまで設定が可能です。
例えば以下の用に設定したとします。
%files
%attr(0755,root,root) /usr/bin/sample-script
これは/usr/bin/sample-scriptというファイルが0755のパーミッションでuserIDがroot、groupIDがrootでインストールされることをあらわします。
%defattr
%defattrを用いると以降すべてのファイルのパーミッション、userID、grouIDが指定出来ます。
例えば以下の用に設定したとします。
%files
%defattr(0755,root,root)
/usr/bin/sample-script1
%attr(0644,root,root) /usr/bin/sample-script2
/usr/bin/sample-script3
この場合sample-script1とsample-script3は%defattrで設定したパーミッション0755、userIDがroot、groupIDがrootになります。
ただし、sample-script2は%attrで上書きしているのでパーミッションが0644となります。
%config
設定ファイル(書き換えられる可能性のあるファイル)であることを示します。
%configにはnoreplaceとmissingokがあり、何も指定しなかった場合はデフォルトでnoreplaceになります。
noreplace
%config(noreplace)としたファイルに変更があった場合、アンインストール時や再インストール時に新しいファイルに置き換えられなくなります。
具体的に以下のような記述をしたとします。
%files
%config(noreplace) /etc/sample/sample.conf
インストールされた設定ファイルに変更を加えます
$ echo "推しメンすちすちビーム" >> /etc/sample/sample.conf
パッケージをアンインストールすると、設定ファイルはリネームして残されます。
$ sudo rpm -e sample-script
警告: /etc/sample/sample.conf は /etc/sample/sample.conf.rpmsave として保存されました。
$
$ ls -1 /etc/sample
sample.conf.rpmsave
パッケージを再インストールすると再度設定ファイルが作られます。
$ sudo rpm -ih sample-script-1.0-1.el7.x86_64.rpm
################################# [100%]
更新中 / インストール中...
################################# [100%]
$ ls -1 /etc/sample/
sample.conf
sample.conf.rpmsave
またパッケージ側で設定ファイルに変更があった場合は、新しい設定ファイルはインストールされずに、
ファイル名に.rpmnewを付けた形でインストールされます。
$ echo "推しメンすちすちビーム" >> /etc/sample/sample.conf
$
$ sudo rpm -Uh sample-script-1.0-2.el7.x86_64.rpm
################################# [100%]
更新中 / インストール中...
警告: /etc/sample/sample.conf は /etc/sample/sample.conf.rpmnew として作成されました。
################################# [ 50%]
整理中 / 削除中...
################################# [100%]
$
$ ls -1 /etc/sample/
sample.conf
sample.conf.rpmnew
missingok
%config(missingok)としたファイルは、ファイルが存在しなくてもエラーになりません。
これはrpm -Vコマンドなどでチェックしたときにエラーにならないようにするためのものです。
更新履歴
rpmパッケージの更新履歴を英語で記述します。
記述するフォーマットはパッケージによって微妙に異なりますが、基本的には以下のフォーマットで大丈夫です。
* 曜日 月 日 西暦 パッケージャーの名前 <メールアドレス> バージョン-リリース番号
- 更新内容
これを最新の更新情報が上に来るように書き加えていきます。
実際は下記のようになるとおもいます。
%changelog
* Thu dec 6 2018 shun kawai <shun@example.com> 1.0-2
- second release
* Wed dec 5 2018 shun kawai <shun@example.com> 1.0-1
- first release
最後に
自作のスクリプトなどでもrpmパッケージになっていればrpmコマンドやyumコマンドでインストール・アンインストールができるから便利!
debパッケージも作れるようになるとなお良いのかもしれません。勉強します。
参考にしたサイト