52
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OSSTechAdvent Calendar 2018

Day 8

specファイル大解剖

Last updated at Posted at 2018-12-07

はじめに

みなさんは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マクロ

主にソースコードの展開をおこないます。
Source0hogehoge.tar.gzのようなファイルが指定されていることが想定されています。

実際の動きとしては下記の通り

  1. %{name}-%{version}で指定されているディレクトリを削除します。
  2. Source0に指定されているファイルをgzipで解凍し、tarで展開します。
  3. %{name}-%{version}で指定されているディレクトリにcdで移動する。
  4. 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-script1sample-script3%defattrで設定したパーミッション0755、userIDがroot、groupIDがrootになります。
ただし、sample-script2%attrで上書きしているのでパーミッションが0644となります。

%config

設定ファイル(書き換えられる可能性のあるファイル)であることを示します。
%configにはnoreplacemissingokがあり、何も指定しなかった場合はデフォルトで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パッケージも作れるようになるとなお良いのかもしれません。勉強します。

参考にしたサイト

52
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
52
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?