こんにちは。サイボウズ・ラボ アルバイトの宮川(@miyagaw61)です。
今回は、Gitでソースコードを管理するソフトウェアにおいて、git-buildpackage(gbp)コマンドを使って複数のLinuxディストリビューションやそのバージョン毎に効率良くdebパッケージ化を行い、メンテナンスする方法について紹介したいと思います。
環境
Ubuntu Server 16.04 LTSで作業していますが、 Ubuntuの14.04や18.04でも問題なく動くと思います。
事前知識と準備
deb-version
deb-versionとはdebianパッケージで用いられているバージョン番号形式のことで、{アップストリームバージョン}
もしくは{アップストリームバージョン}-{リビジョン}
という形式で表されます。
アップストリームバージョンは英数字(A-Za-z0-9
)と記号(.+:~
)が使用可能で、先頭は数字である必要があります。
一般的には「X.Y.Z」という形式で表されることが多く、XはMajor番号、YはMiner番号、ZはPatch番号と呼ばれます。
それぞれは次の意味を持っています。
- Major番号:後方互換性がない変更の時にインクリメントする番号
- Minor番号:後方互換性がある変更の時にインクリメントする番号
- Patch番号:バグ修正の時にインクリメントする番号
リビジョンは英数字(A-Za-z0-9
)と記号(+.~
)が使用可能で、付けるか否かは自由です。
一般的には先頭に数字が置かれます。そのあとに~
か+
が置かれ、任意の文字列が置かれることもあります。
アップストリームバージョンもリビジョンも、いずれも以下のようなルールを持っています。
0.0 < 0.5 < 0.10 < 0.99 < 1 < 1.0~rc1 < 1.0 < 1.0+b1 < 1.0+nmu1 < 1.1 < 2.0
詳しくはこちらを参照ください。
上流と下流の用意
以下の二つのリポジトリを用意しておいてください。
- 上流リポジトリ: debianディレクトリの無い大元のソースコード管理リポジトリです。このリポジトリは自分ではコントロールできないものとします。
例では
git@github.com:miyagaw61/test.git
を使います。後で中身は作りますので、最初は空で構いません。 - 下流リポジトリ:debianディレクトリの差異を管理するリポジトリです。
例では
git@github.com:miyagaw61/pkg-test.git
を使います。上流リポジトリ同様、最初は空で構いません。
リポジトリ名やコミットメッセージなどは、適宜変更してください。
作業の流れ
- 下流リポジトリを作成してdebパッケージをビルドできるようになるまで
- debパッケージ側で管理するパッチの更新
- 上流の変更への追従
新規作成・初期設定
では、さっそく始めていきましょう。 上流リポジトリが無ければ何も始まりません。 ここでは、誰かが上流リポジトリで新規バージョンをリリースしたものとします。その内容は以下のようなものであるとします。
$ git clone git@github.com:miyagaw61/test.git $ cd test $ echo "echo hello" > hello.sh $ git add hello.sh $ git commit -m "Add hello.sh" $ git tag upstream/1.0.0 $ git push origin master $ git push origin upstream/1.0.0 $ cd ..
次に、予め作成しておいた空の下流リポジトリをクローンします。
$ git clone git@github.com:miyagaw61/pkg-test.git $ cd pkg-test
以降、断わりがない限り下流リポジトリでの作業となります。 上流リポジトリをfetchします。
$ git remote add upstream git@github.com:miyagaw61/test.git $ git fetch upstream $ git checkout -b upstream upstream/master
任意のDebian系ディストリビューションの任意のバージョンにおいて、debianディレクトリ以下やソースコードなどをそのバージョンに合わせて管理するための、専用のブランチを作成します。
$ git checkout -b xenial
今回はxenial(Ubuntu16.04)環境で解説していくため、名前はxenialとしました。
次に、debianディレクトリを作成します。 どんな方法で作成しても大丈夫ですが、今回は簡単のためdh_makeを使用します。パッケージクラスはsingleに設定します。
$ dh_make --createorig -p test_1.0.0 -s -y
ここでのtest_1.0.0
は、{パッケージ名}_{アップストリームバージョン}
の形式にしてください。
このとき、{アップストリームバージョン}
は先ほどのupstreamに付けたタグと同じにしてください。
debian/{パッケージ名}.installファイルを作成します。 このファイルでインストールするファイルとインストール先のパスを指定します。
$ echo "hello.sh usr/bin" > debian/test.install
スペース区切りで、左側にインストールするファイルをパッケージのディレクトリからの相対パスで、右側にインストール先のパスをルートディレクトリからの相対パスで記述します。 こちらに書いてあるように、DebianのPolicyにより/usr/local以下にパッケージをインストールすることは禁止されています1。
変更をadd・commitします。
$ git add debian $ git commit -m "Add debian directory"
ビルドが成功するか確認します。
$ gbp buildpackage -us -uc --git-ignore-new
-us -uc
オプションを使用すると署名を行いません。
--git-ignore-new
オプションを使用するとstagingされていないファイルが存在していてもビルドできるようになります。
ビルドが成功したら、インストールして実行してみます。
$ sudo dpkg -i ../test_1.0.0-1_amd64.deb $ hello.sh
"hello"と出力されていれば正常です。
ビルドすると、debianディレクトリ以下に新たにファイルがいくつか生成されます。 無事ビルド・インストール・実行の全てが正常に行えることを確認したら、まずはビルド時に生成されたファイルを全て削除してビルド前の状態に戻しましょう。
$ git status $ rm -rf debian/{debhelper-build-stamp,files,test.debhelper.log,test.substvars,test}
ビルド直前のデータはコミットされているので、rm -rf
を使う代わりに以下のコマンドでも消せます。
$ git clean -df
PbuilderやCowbuilderなどを使用してビルドしたい方は、ビルド時に--git-pbuilder
オプションを追加で使用してください2。
事前に以下の方法でベースイメージ(例:/var/cache/pbuilder/cow.base)を作成しておく必要があります。
~/.pbuilderrc
とDIST
環境変数の内容は適宜書き換えてください。
$ vim ~/.pbuilderrc $ cat ~/.pbuilderrc COMPONENTS="main restricted universe multiverse" $ DIST=xenial BUILDER=cowbuilder git-pbuilder create
そして、現在のバージョンのタグを作成します。
現在のバージョンとは、現在のchangelog(debian/changelog
ファイル)に記述されている最新のバージョンのことです。
次のコマンドで、現在のバージョンのタグを自動で作成できます。(--git-tag-only
により実際のビルドはしません)
$ gbp buildpackage --git-tag-only --git-ignore-branch
現在のchangelogの最新バージョンは"1.0.0-1"なので、"debian/1.0.0-1"というタグが自動で作成されました。 上流と下流のタグはそれぞれ、次に示す命名規則に従っている必要があります。
- 上流に付けるタグは
upstream/{アップストリームバージョン}
という表記でなければならない(--git-upstream-tag
で変更可能) - 下流に付けるタグは
debian/{アップストリームバージョン}
もしくはdebian/{アップストリームバージョン}-{リビジョン}
という表記でなければならない(--git-debian-tag
で変更可能) - 下流のタグに含まれるバージョン文字列はchangelogに記述されているバージョン文字列と完全に一致していなければならない
--git-upstream-tag
や--git-debian-tag
などのオプションを毎回指定するのが面倒な場合は、debian/gbp.conf
に設定を記述することで省略することができます3。
無事タグの生成を済ませたら、タグをpushしましょう。
$ git push origin debian/1.0.0-1
pristine-tarについて
orig.tar.gzファイルは上流メンテナが提供するソースコードと同じ内容を含むアーカイブです。orig.tar.gzファイルは誰でも生成できるのですが、上流メンテナと全く同じコードをビルドしたとしても、環境によってはファイルとしては異なるものになってしまうことがあります。しかし、上流メンテナと全く同じorig.tar.gzを生成しなければいけない場合があります。そういう時は、pristine-tar
を使用してください。
pristine-tar
は、上流メンテナと全く同じorig.tar.gzファイルを再生成するためのpristine-tarデータの作成や、pristine-tarデータを用いて上流メンテナと全く同じorig.tar.gzの生成などを行うことができるコマンドです。
gbp buildpacakgeコマンドには、--git-pristine-tar
と--git-pristine-tar-commit
という二つのオプションがあります。これらは、pristine-tar
を内部で使用しており、上流メンテナと全く同じorig.tar.gzを用いてdebパッケージ化する際に役立ちます。
組み合わせごとの詳細な内部処理を解説します。以下の4パターンがあります。
--git-pristine-tar
も--git-pristine-tar-commit
も指定しなかった場合: changelogからアップストリームバージョンを抽出します。それを対応する上流のタグに変換し(変換方法は--git-upstream-tag
オプションで指定可能)、そのタグが付いたコミットを探し、そのソースコードからorig.tar.gzを生成します(既にorig.tar.gzを持っていたら生成せずにそれを使います)。そして、そのorig.tar.gzを用いてdebパッケージ化します。--git-pristine-tar-commit
のみを指定した場合: aの手法でビルドすると同時に、ビルドするバージョンのpristine-tarデータがpristine-tarブランチに存在していない場合、ビルドに使われたorig.tar.gzからpristine-tarデータを生成し、pristine-tarブランチにコミットします。--git-pristine-tar
のみを指定した場合: changelogからアップストリームバージョンを抽出します。そのバージョンのpristine-tarデータがpristine-tarブランチに存在していないとエラーになります。存在していたらそこからorig.tar.gzを再生成(その再生成したorig.tar.gzと既に持っているorig.tar.gzのハッシュ値が異なる場合はエラー)し、そのorig.tar.gzを用いてdebパッケージ化します4。--git-pristine-tar
と--git-pristine-tar-commit
を両方を指定した場合: changelogからアップストリームバージョンを抽出します。そのバージョンのpristine-tarデータがpristine-tarブランチに存在していれば--git-pristine-tar
オプションのみ有効に、存在していなければ--git-pristine-tar-commit
オプションのみ有効になります。
通常は、--git-pristine-tar
と--git-pristine-tar-commit
の二つのオプションを併用してビルドする運用で問題無いと思われます5。
パッチの更新
パッケージ管理者がdebianディレクトリ以外に存在するソースコードを修正したい場合は、オリジナルソースコードに対するパッチファイルを作成し、quiltコマンドで管理します。 そのパッチファイルはdebian/patchesに格納されます。 gbpを用いると、quiltを直接使わずにgitと連携してパッチを管理することができます。 debian/patches以下のパッチファイルは本記事ではquiltパッチと呼んで区別することにします。
先程作成した、特定バージョンにおいてのパッケージ管理用ブランチに移動します(ここではxenialブランチを指します)。
$ git checkout xenial
xenialブランチにいる状態でgbp pq import
というコマンドを実行するとpatch-queue/xenialというブランチが自動で作成され、xenialブランチのdebian/patches以下にあるパッチファイルが全てコミットとして再現されます。
これらのpatch-queue/BRANCHをここではpatch-queueブランチと呼ぶことにします。
既に対応するpatch-queueブランチが存在する場合は作成ではなくgbp pq switch
かgbp pq rebase
(詳細は後述)で移動する必要があります。
$ gbp pq import # もしくはgbp pq switchかgbp pq rebase
quiltパッチがひとつも存在しない状況では、図にすると次のようになります。
quiltパッチを作成,編集,削除したい時はpatch-queueブランチで開発します。 通常の開発と同様にファイルを編集し、commitします。
$ sed "s/hello/hello, world/g" -i hello.sh $ git add hello.sh $ git commit -m "Add world to hello.sh"
開発がひと段落したら、xenialブランチとpatch-queue/xenialブランチとの差分コミットをパッチファイルという形にしてxenialブランチへ送ります。
--commit
オプションを付けることで、自動でxenialブランチへの変更コミットも行ってくれます。
$ gbp pq export --commit
patche-queue/xenialブランチ上ではオリジナルソースコードへの変更がそのままgitコミットとして存在しますが、xenialブランチ上ではdebian/patches/以下のパッチファイルを追加するgitコミットとして存在します。 xenialブランチに戻ってきたら、changelogを更新します。 次のコマンドで、バージョン1.0.0-2+xenialとしてchangelogを更新できます。
$ gbp dch -N 1.0.0-2+xenial --commit --ignore-branch
変更前の changelog:
test (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> -- miyagaw61 <miyagaw61@unknown> Fri, 08 Jun 2018 15:30:58 +0900
変更後の changelog:
test (1.0.0-2+xenial) UNRELEASED; urgency=medium * Add world to hello.sh -- miyagaw61 <miyagaw61@gmail.com> Fri, 08 Jun 2018 15:54:26 +0900 test (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> -- miyagaw61 <miyagaw61@unknown> Fri, 08 Jun 2018 15:30:58 +0900
--commit
オプションは自動でcommitも行ってくれるオプション、--ignore-branch
オプションはmasterブランチ以外でも更新できるようにするオプションです。
ちなみに、-N
オプションを付けなければ現在のバージョンにパッチを追加、-R
オプションを付けるとchangelog中のUNRELEASEDが自分のマシンのOSバージョン(ここではxenial)に書き換わり、リリースに向けた最終調整のためにエディタが起動します。
同じバージョンのタグも生成します。
$ gbp buildpackage --git-tag-only --git-ignore-branch
"debian/1.0.0-2+xenial" というタグが git リポジトリに追加されました。
masterブランチ以外でタグを生成する時は--git-ignore-branch
オプションが必要です。
ビルドが成功するか確認します。
$ gbp buildpackage -us -uc --git-ignore-branch
現在のchangelogの最新バージョン名は"1.0.0-2+xenial"なので、対応するアップストリームバージョンは"1.0.0"になります。 そのため、今回は"upstream/1.0.0"のタグが指しているコミットに対応するソースコードを使ってビルドします。 ビルドが成功したら、インストールして実際に実行してみましょう。
$ sudo dpkg -i ../test_1.0.0-2+xenial_amd64.deb $ hello.sh
"hello, world"と出力されていれば正常です。 正しく変更が適応されていることがわかります。
また、先程と同様に、ビルドによって新たにファイルが作成されているため、全てを削除し、ビルド前の状態に戻します。
$ git clean -df
ビルド・インストール・実行が成功することを確かめたら、changelogを最終調整し、リリースしましょう。
$ gbp dch -R --commit --ignore-branch
変更前の changelog:
test (1.0.0-2+xenial) UNRELEASED; urgency=medium * Add world to hello.sh -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp> Fri, 14 Sep 2018 13:24:52 +0900 test (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> -- miyagaw61 <miyagaw61@unknown> Fri, 14 Sep 2018 11:49:46 +0900
変更後の changelog:
test (1.0.0-2+xenial) xenial; urgency=medium * Add world to hello.sh -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp> Fri, 14 Sep 2018 13:24:52 +0900 test (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> -- miyagaw61 <miyagaw61@unknown> Fri, 14 Sep 2018 11:49:46 +0900
タグを付けなおします。
$ gbp buildpackage --git-tag-only --git-ignore-branch --git-retag
ブランチとタグをpushします。
$ git push origin xenial $ git push origin debian/1.0.0-2+xenial
上流の変更への追従
ここでは上流のソースコードがバージョンアップなどによって変更された場合に、 下流リポジトリのブランチで追従する方法について説明します。 上流の更新に追従する場合、まずxenialブランチを上流の適切なコミット(通常はバージョンタグ) に対してrebaseさせ、次に、patch-queue/xenialブランチをxenialブランチに対してrebaseさせるという 二段階の工程を踏みます。
準備
準備として、まずは更新された上流を用意します。
$ cd ../test $ git checkout master $ sed "s/hello/hi/g" -i hello.sh $ git add hello.sh $ git commit -m "Use hi instead of hello" $ git push origin master $ git tag upstream/2.0.0 $ git push origin upstream/2.0.0 $ cd ../pkg-test $ git checkout upstream $ git pull --rebase upstream master:upstream
タグの作成を忘れないでください。 上流に適切なタグがついていない場合は、下流でタグを付けて管理してください。
今の状況を図にすると次のようになります。
追従
追従したい下流ブランチへ移動します。
$ git checkout xenial
上流ブランチに対してrebaseします。
$ git rebase upstream
通常であればコンフリクトは起きないはずです。xenialブランチではdebianディレクトリ以下しか編集しておらず、 上流にはdebianディレクトリが存在しないからです。 無事rebaseできたら、次のコマンドでpatch-queue/xenialブランチへの移動とxenialブランチでのrebaseを行います。 これは、quiltパッチがgitコミットとして表現されているpatch-queue/xenialブランチをxenialブランチにrebaseすることで、quiltパッチを上流の新しいバージョンに対してrebaseすることが目的です。
$ gbp pq rebase
ここでコンフリクトが発生したら、解消します。
$ cat hello.sh $ cat hello.sh | grep "hello, world" | sed "s/hello/hi/g" > tmp; mv tmp hello.sh # もしくはvim hello.sh $ cat hello.sh $ git add hello.sh $ git rebase --continue
次に、xenialブランチとpatch-queue/xenialブランチとの差分コミットをパッチファイルという形にしてxenialブランチへ送ります。
$ gbp pq export --commit
changelogを更新します。一気にリリースしてしまいます。
$ gbp dch -R -N 2.0.0-1+xenial --commit --ignore-branch
変更前の changelog:
test (1.0.0-2+xenial) xenial; urgency=medium * Add world to hello.sh -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp> Fri, 14 Sep 2018 13:24:52 +0900 test (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> -- miyagaw61 <miyagaw61@unknown> Fri, 14 Sep 2018 11:49:46 +0900
変更後の changelog:
test (2.0.0-1+xenial) xenial; urgency=medium * Use hi instead of hello * Add debian directory * Add world to hello.sh * Update changelog for 1.0.0-2+xenial release * Update changelog for 1.0.0-2+xenial release * Rediff patches -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp> Fri, 14 Sep 2018 13:43:56 +0900 test (1.0.0-2+xenial) xenial; urgency=medium * Add world to hello.sh -- miyagaw61 <miyagaw61@miyagaw61-ubuntu.in.labs.cybozu.co.jp> Fri, 14 Sep 2018 13:24:52 +0900 test (1.0.0-1) unstable; urgency=medium * Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP> -- miyagaw61 <miyagaw61@unknown> Fri, 14 Sep 2018 11:49:46 +0900
タグを作成します。
$ gbp buildpackage --git-tag-only --git-ignore-branch
ビルド・インストール・実行ができることを確認します。
$ gbp buildpackage -us -uc --git-ignore-branch $ sudo dpkg -i ../test_2.0.0-1+xenial_amd64.deb $ hello.sh
"hi, world"と出力されれば正常です。 正しく変更が適応されていることがわかります。
ビルド前の状態に戻します。
$ git clean -df
git rebase upstream
によってFast-forwardで適用できなくなってしまったため、このままgit push origin xenial
してしまうとrejectされてしまいます。
なので、force pushします。
$ git push -f origin xenial
タグをpushします。
$ git push origin debian/2.0.0-1+xenial
複数人で下流リポジトリを管理している場合の注意
複数人で管理している下流リポジトリでforce pushをしてしまうと、自分以外の人がリモートに反映した情報を消してしまう可能性があります。 そのような場合には、複数人が同じ名前のブランチで作業したり、同じ名前のタグを付けることが無いように運用する必要があります。GitHub等を使っている場合はforkしてpull requestするのが王道でしょうか。 patch-queue ブランチは必ずしも共有しなくても良いと思います。
debianディレクトリ以下の変更
最後に、debianディレクトリ以下のファイルの変更方法を解説します。 debianディレクトリ以下のファイルは、patch-queueブランチで手を入れるとビルド時に怒られてしまいます。 patch-queueブランチはあくまでquiltパッチを管理するためのブランチですので、 debianディレクトリ以外のファイルを変更するときのみ使います。 debianディレクトリ以下のファイルは、xenialブランチで直接編集し、コミットしましょう。
$ git checkout xenial $ sed "s@<insert the upstream URL, if relevant>@https://miyagaw61.github.io@g" -i debian/control
ビルドし、変更が適応されているか確認してみましょう。
$ gbp buildpackage -us -uc --git-ignore-branch --git-ignore-new $ dpkg -I ../test_2.0.0-1+xenial_amd64.deb
Homepage: https://miyagaw61.github.io
となっていれば変更が正しく適応されています。
ビルド前の状態に戻します。
$ git clean -df
add・commit・pushします。
$ git add debian/control $ git commit -m "Add Homepage-Url" $ git push origin xenial
最後に
本記事では複数のLinuxディストリビューションやそのバージョン毎に効率良くdebパッケージ開発を行う方法を紹介しました。 我々はこのノウハウを得て、これまでオリジナルのリポジトリ上で直接debianディレクトリを管理していた walb-toolsを walb-tools-pkgリポジトリと分離させました。 本記事が同様のdebパッケージ管理をしたい方々のお役に立てば幸いです。
参考
- Debian package 関連
- git-buildpackage 関連
- 東京エリアDebian勉強会 Debianパッケージング道場
- Building Debian Packages with git-buildpackage
- gbp-pq: Building Debian Packages with git-buildpackage
- gbp-dch: Building Debian Packages with git-buildpackage
- Working with Patches: Building Debian Packages with git-buildpackage
- Gitリポジトリが公開されているソフトウェアのDebianパッケージをGitで管理する方法 - Qiita
- git-buildpackageのpatch-queue機能を試してみた
- Git で deb パッケージング - uchan note
- git-buildpackageでdebパッケージをビルドしてPPAにアップロードする手順
- git-buildpackageでdebパッケージをビルドしてPPAにアップロードする手順
- バージョニング
-
そのため、何の対策もせずに/usr/local以下にパッケージを直接インストールしようとすると、
dh_usrlocal
というツールの実行に失敗してしまいます。どうしても/usr/local以下にパッケージを直接インストールしたい場合は、こちらに書いてあるように、debian/rulesにoverride_dh_usrlocal:
の一行を追記し、dh_usrlocal
の実行を無効化する必要があります。↩ -
PbuilderやCowbuilderなどを使用するとビルド時にdebianディレクトリ以下に新たなファイルが生成されずに済みますし、依存パッケージの記述漏れに悩むことがなくなります。↩
-
gbp.confを配置可能なパスはdebianディレクトリ以外にもいくつかあります。↩
-
このオプションが有効な時、
--git-upstream-tag
オプションは効果が無くなります。--git-upstream-tag
オプションは飽くまでchangelogから抽出したアップストリームバージョンからそれに対応する上流に付けられているタグに変換するための変換方法を指定するオプションであり、--git-pristine-tar
オプションが有効な時はその変換処理が必要無いからです。↩ -
今回は簡略化のため、以降のコマンド例でも
pristine-tar
は使用していません。↩