uchan note

プログラミングや電子工作の話題を書きます

Git で deb パッケージング

Git で管理しているソフトウェアを deb パッケージにしたいことがあります。 本記事は、1 つの Git リポジトリの中でソフトウェア本体と deb パッケージのメタデータを管理する方法を説明します。

2016/02/29追記:ここで紹介するブランチ構成には問題があることが分かりました。 debian ブランチを master に rebase した後、debian ブランチを push できないのです。 rebase する代わりに debian_1.0.1 のようなブランチを作り、cherry-pick するといける気がします。

目次

1 つのリポジトリメタデータも管理する

deb git」などと検索してヒットするのは、ソフトウェア本体とパッケージを作る人が異なり、そのため リポジトリが別 である前提である記事が多いです。 しかし、私は作ったソフトウェアを自分で deb パッケージにしたかったので、別リポジトリにしたくないと思っていろいろ調べました。

社内 apt リポジトリの運用と deb パッケージビルドの話Debianization with git-buildpackage には 1 つのリポジトリで管理する話が紹介されています。

後者の記事はコマンドレベルで詳しく書いてあるのでよく見ると、どうやら ノンネイティブ パッケージの作り方であることが分かります。 なぜなら .orig.tar.gz ファイルを作っているからです。 ノンネイティブパッケージの作り方ではこのように、ソースコードを一旦 $PACKAGE_$VERSION.orig.tar.gz というアーカイブにする必要があります。

せっかく Git 上にソースコードがあるのに、それをアーカイブにするというのはなんとも微妙ではないでしょうか?何しろ面倒ですよね。

そこで ネイティブ パッケージの作り方を試してみました。 ネイティブ Debian パッケージ - Debian 新メンテナーガイド によると、ローカル、つまり個人や社内で使うためにパッケージングする場合、ネイティブパッケージも選択肢として悪くないようです。 このやり方なら事前に .orig.tar.gz を生成する必要がなく、リポジトリの構成も格段に分かりやすくなります。

debian パッケージングについての一般的な情報

本記事では debian パッケージの作り方に関する一般的な説明はほとんどしません。 ソースコードからパッケージを作る一通りのコマンドは紹介しますので参考にはなるはずですが、 実際にパッケージを作る際は次の文書にさらっと目を通すことをお勧めします。 私も実際に読んでみて、一読の価値がある文書だと思っています。

実験環境

以下、実際にコマンドを動かしながら実験します。 Ubuntu 15.10 で動作確認しています。

実験に必要なコマンドは次のようにインストールしました。

$ sudo apt-get install packaging-dev cowbuilder

ソフトウェア本体とメタデータを作る

ここでは実験として、master ブランチでソフトウェア本体を管理し、debian ブランチでパッケージングのためのメタデータを管理してみます。 master ブランチには機能単位でコミットがあり、きりのいいコミットにバージョン番号が付くような運用を仮定します。

実験のためにディレクトリを掘り、ビルド対象のソースコードを準備します。

$ mkdir -p deb/hello/hello
$ cd deb/hello/hello
$ vim hello.c

hello.c はどんなプログラムでもいいですが、ビルドして実行できると実験が楽しいと思います。 準備できたら Git にコミットします。

$ git init
$ git add hello.c
$ git commit -m "initial commit"

次にソースコードをビルドし、インストールするための Makefile を書きます。

$ vim Makefile

Makefile は次のような内容です。 (install コマンドを変数化していないなど GNU のコーディング規約 には適合しませんが、簡単のため敢えてそうしています)

all:
        gcc -o hello hello.c

clean:
        rm -f hello

install:
        install -d $(DESTDIR)/usr/bin
        install hello $(DESTDIR)/usr/bin/hello

これらをバージョン 1.0.0 の候補としてコミットします。

$ ls
Makefile  hello.c
$ git add Makefile
$ git commit -m "release candidate 1.0.0"

図 1 の master HEAD がちょうどこのコミットを指しているとしましょう。

f:id:uchan_nos:20160228113531p:plain

バージョン 1.0.0 の deb パッケージを作るために、このコミットをベースに debian ブランチを作ります。

$ git branch debian
$ git checkout debian

これは次のように省略してもいいです。

$ git checkout -b debian

これで図 2 の状態になりました。 早速 deb パッケージのメタデータを生成します。

$ dh_make --native --multi -p hello_1.0.0

--native を指定してネイティブパッケージを作るのがポイントです。 実行すると debian ディレクトリが生成されていますので、必要に応じて編集します。

$ vim debian/control
$ vim debian/changelog

最後に hello バイナリを /usr/bin 以下にインストールするように、hello.install ファイルへ記載します。

$ echo "/usr/bin/hello" >> debian/hello.install

調整し終わったら、追加したメタデータをコミットします。

$ git add debian/*
$ git commit -m "add debian metadata"

ここまで実行すると図 3 となります。

DESTDIR と hello.install

Makefile の install ターゲットと hello.install ファイルで同じような記述があるのが分かりにくいので、少し説明します。

install ターゲットで使っている DESTDIRdeb パッケージングに固有ではなく、GNU make で一般的に使われる変数です。 ソフトウェアのインストール先を変更するのに使います。

ターミナルから手動で sudo make install を実行すると、DESTDIR は設定されず空文字列となります。 したがって hello バイナリは /usr/bin/hello にコピーされます。

一方、後ほど出てくる dpkg-buildpackage によるビルドでは、 DESTDIR が 一時ディレクトリ へのパスに設定されます。 したがって hello バイナリは debian/tmp/usr/bin/hello にコピーされます。

次に hello.install ファイルが役立ちます。 hello.install で指定したファイルは debian/tmp ディレクトリから debian/hello ディレクトリへとコピーされます。 最終的に debian/hello に配置されたファイルが deb パッケージに含まれるという仕組みです。

ちょっと複雑なのは、私たちが --multi複数バイナリを扱える deb を生成しようとしているためです。 複数バイナリのパッケージでは、amd64x86 でインストールすべきファイルセットが異なるかもしれないため、この仕組みが役立ちます。

ちなみに、パスを変更しながらインストールしたいときは、hello.install の中で

resource/hello.conf etc/init

などと書きます。このパッケージをインストールすると /etc/init/hello.conf というファイルが存在することになります。

ビルド実験

ここで、ちょっとビルド実験をしてみましょう。

ビルドによりいくつかのファイルが debian ディレクトリに生成されるので、 ビルドの前メタデータをコミットしておくと良いでしょう。 そうしないと、余計なファイルをコミットに含めてしまう可能性があります。

$ dpkg-buildpackage
$ ls ..
hello  hello-doc_1.0.0_all.deb  hello_1.0.0.dsc  hello_1.0.0.tar.xz
hello_1.0.0_amd64.changes  hello_1.0.0_amd64.deb

ビルドするといくつかのファイルが、一つ上の階層に生成されます。 バージョン番号は debian/changelog で指定したものになっているはずです。

deb パッケージの中身を見て、正しく /usr/bin/hello が含まれているか確認します。

$ dpkg -c ../hello_1.0.0_amd64.deb
./
./usr
./usr/bin
./usr/bin/hello
...

おお、ビルドが成功しているようです。

Makefile に all, clean, install ターゲットをそれぞれ定義するのがポイントです。 これらが定義されていると dpkg-buildpackage の実行過程で自動的に認識され、呼び出されます。 ("all" に限っては名前は重要ではありません。一番上に書いてあれば何でも良いです。)

ビルドすると、debian ディレクトリにいくつかファイルが追加されるのが分かると思います。 これらは 毎回自動で 作られるものなので、Git で管理する必要はないはずです。 それらを .gitignore に登録してしまいましょう。

$ echo "files" >> debian/.gitignore
$ echo "hello-doc" >> debian/.gitignore
$ echo "hello" >> debian/.gitignore
$ echo "*.debhelper.log" >> debian/.gitignore
$ echo "*.substvars" >> debian/.gitignore
$ echo "tmp" >> debian/.gitignore
$ echo "hello" >> .gitignore

.gitignore をコミットしておくと良いでしょう。 ここではコミットツリーを綺麗に保つために --amend していますが、もちろん普通にコミットしても大丈夫です。

$ git add .gitignore debian/.gitignore
$ git commit --amend -C HEAD

バージョン 1.0.1 を開発する

さて、めでたくバージョン 1.0.0 の deb パッケージを作成できましたので、ソフトウェア本体の開発を続けてバージョン 1.0.1 をリリースしてみます。 master ブランチでいくつかコミットします。

$ git checkout master
$ hack hack hack...
$ git commit -m "hoge"

これで図 4 の状態になります。 図では仮に 2 コミット進んだ状態にしていますが、いくつでも実験には影響しません。 master HEAD がバージョン 1.0.1 の候補のコミットだとします。

f:id:uchan_nos:20160228113537p:plain

deb パッケージを作るために debian リポジトリを master HEAD に対して rebase します。 ただ、すぐに rebase してしまうとバージョン 1.0.0 用のメタデータが失われてしまいます。

過去のリリースは いつでも ビルドできるようにしておきたいところです。 そのためにはバージョン 1.0.0 のソースコードとともに 対応するメタデータ を保存しておく必要があります。 そんなときは Git のタグ機能が便利です。

$ git checkout debian
$ git tag debian_1.0.0

こうすることで、いつでも 1.0.0 のビルドをすることができますね。 この段階で図 5 の状態になっています。

さて、改めて debian ブランチを rebase します。

$ git rebase master

これで図 6 の状態になりました。 debian/changelog に 1.0.1 の項目を書き足します。

$ dch
hello (1.0.1) UNRELEASED; urgency=medium

  * version 1.0.1

 -- Tarou Yamada <hoge@foo.bar>  Sun, 28 Feb 2016 16:33:09 +0900

hello (1.0.0) unstable; urgency=low

  * Initial Release.

 -- Tarou Yamada <hoge@foo.bar>  Sun, 28 Feb 2016 16:21:53 +0900

debian/changelog の編集が終わったら変更点をコミットします。

$ git commit -a -m "update changelog 1.0.1"

現在、リポジトリは図 7 の状態になっているはずです。

f:id:uchan_nos:20160228164537p:plain:w350

ビルド実験 2

dpkg-buildpackage でビルドしてみると、今度は hello_1.0.1_amd64.deb が生成されているはずです!

実際にインストールして動かしてみるのも楽しいでしょう。 次のようにします。

$ sudo dpkg -i ../hello_1.0.1_amd64.deb
$ which hello
/usr/bin/hello
$ hello
hello, deb package!

もちろん、最後の出力結果は hello.c の内容によって変わります。 deb ですからアンインストールも簡単です。

$ sudo dpkg -r hello

クリーンビルド

最後に、deb パッケージを pbuilder を使ってクリーンビルドする方法を説明しておきます。 本題とは離れてしまいますが、備忘録として書くことにしました。

pbuilder (cowbuilder) を使うには、初回だけベースイメージをセットアップするために次のコマンドを実行します。

$ sudo cowbuilder --create --components "main universe multiverse restricted"

次のコマンドで deb をビルドします。

$ pdebuild --pbuilder cowbuilder --debbuildopts -j4

成果物は /var/cache/pbuilder/result/ に生成されます。