Mercurial Queues の用法を真っ正直に話題にするのは簡単ですが、少々抑制を効かせて、込み入った開発環境での作業に役立つような、あまり利用されない機能を幾つか説明しようと思います。
この章では、 Linux カーネル向けの Infiniband デバイスドライバ開発において、管理に用いていた技法を使用例として取り上げます。このデバイスドライバは(一般のデバイスドライバ程度には)大きく、 35 のソースファイルにまたがった 25,000 行からなっており、少数の開発チームにより保守されています。
この章で扱っている対象は Linux に特化したものですが、自身が所有していないコードを元に多くの開発を行う必要がある局面で、同様の方針が適用できるでしょう。
Linux カーネルは頻繁に変更され、内部的には決して安定していません。開発者はリリースの間に度々思い切った変更を行います。このため、 Linux カーネルの特定のリリース版で機能するドライバーの版は、概して他の版においてはコンパイルすら通らない場合があります。
ドライバの保守を行うためには、いくつかの個別の Linux の版を意識する必要があります。
複数の異なる環境を対象としなければならない一連のソフトウェアの保守には、2つの “標準的な” 方法があります。
1つ目の方法は、それぞれが単一の環境を対象とする複数のブランチを管理する方法です。この方法の問題点は、リポジトリ間での変更 の往来1 において、鉄の規律でもって望む必要が有ることです。新しい機能やバグの修正は “真新しい” リポジトリで始めなければならず、その後で全てのバックポート用リポジトリに浸透させます。バックポートでの変更は、その伝播が更にブランチ限定されま す。所属外のブランチに適用されるようなバックポート向けの変更は、おそらくドライバのコンパイルを妨げるでしょう。
2つ目の方法は、個々のコード片の有効/無効を、意図する対象に依存して切り替えるための条件文で埋められた、単一のソースツリーを保守する方法です。これらの “ifdef” 記述は、 Linux カーネルツリーでは許されていないので、これらを取り除いて綺麗なツリーを生成するための、手動ないし自動の手順が必要です。この流儀で保守されるコードベースは早々に、理解も保守も困難な条件分岐の 「鼠の巣」となるでしょう。
これらのいずれの手法も、正当なソースツリーのコピーを “所有” していない状況には適合しません。標準カーネルと共に配布される Linux ドライバの場合、 Linus 氏のソースツリーは、世界中が正統とみなすコードのコピーから構成されます。上流リポジトリにおける “私の” ドライバは、 Linus 氏のソースツリー上に改変内容が反映されるまでには、知らないうちに見知らぬ人々によって異なる版に改変されているかもしれません。
これらの手法は、上流リポジトリへのパッチの体裁を整えるのを難しくしてしまう、という欠点も持っています。
Mercurial Queues は、これまで述べてきた状況での開発を管理するための、良い候補と言えます。まさにこのような状況において、 MQ は作業を快適にする更に幾つかの付加的機能を持っています。
おそらく、多くの対象環境に対する健全性を保守する方法は、所定の状況ごとに適用される特定のパッチを選択できること、と言えるで しょう。 MQ は、上記の機能を持つ “ガード” (quilt の guards コマンドに由来します)と呼ばれる機能を提供します。まずはじめ に、実験のための簡素なリポジトリを作成しましょう。
この手順により、異なるファイルを操作するので互いには依存性の無い2つのパッチを持つ、小さなリポジトリが得られま す。
条件付き適用の考え方は、任意の単純な文字列からなるガードされた “札” (tag) をパッチに付与しておき、パッチ適用の際に、使用 すべき特定のガードを MQ に対して教える、というものです。あらかじめ選択しておいたガードに応じて、 MQ はガードされたパッチ を適用するか見送るかを決定します。
個々のパッチは任意の数のガードを持つことができ、それぞれのガードはポジティブ(“ガード選択時にパッチを適用する場合”) かネガティブ(“ガード選択時にパッチ適用を見送る”)のどちらかです。ガードを持たないパッチは常に適用されま す。
“hg qguard” コマンドは、どのガードをパッチに適用するかを決定するか、さもなくば現時点で有効なガードを表示します。引数が無 い場合、現在の最上位パッチのガードを表示します。
パッチにポジティブなガードを設定するには、ガード名の接頭辞として “+” を付与します。
パッチにネガティブなガードを設定するには、ガード名の接頭辞として “-” を付与します。
Mercurial は、解釈・手動編集が共に容易な形式で、ガード情報をseries に格納します(言い換えるなら、“hg qguard” コマン ドを利用する必要は無く、series ファイルを直接編集しても構いません)。
“hg qselect” コマンドは、有効にするガードを決定します。ガードが決定することで、次に“hg qpush” を実行した際に MQ が適用 するパッチが決定されます。このコマンドはそれ以外の働きをしません。特に、既に適用済みのパッチに対しては、一切何も行いませ ん。
引数が指定されない場合、“hg qselect” コマンドは、現時点で有効になっているガードを1行に1つづつ表示します。個々の引数 は、適用されるガードの名前とみなされます。
現在選択されているガードの一覧がguards ファイルに格納されていますので、興味があれば見てみるのも良いでしょ う。
“hg qpush” を実行することで、ガード選択の効果を見ることができます。
“+” ないし “-” で始まる名前はガード名にはできません。空白文字を含むものもガード名にはなれませんが、それいがいの大抵の文 字は使用可能です。不正なガード名の使用は、 MQ により警告されます。
ガード選択の変更は、適用されるパッチを切り替えます。
ネガティブなガードがポジティブなガードに優先することを、以下の例で見ることができます。
パッチ適用の有無を判定する際に、 MQ は以下のルールを使用します。
先に述べた Linux カーネル向けの Infiniband デバイスドライバ開発でのパッチ適用では、 Linux カーネルの通常のソースツリーは使 用しません。その代わり、 Infiniband デバイスドライバ開発に関連するソース/ヘッダのみを含むリポジトリを作成し、そこに対して パッチを適用するようにします。このリポジトリのサイズはカーネルリポジトリの 1% に収まるため、作業を行うのも簡単で す。
縮小版のリポジトリを作成したならば、パッチの “適用対象” となるバージョンを選択しま す2 。 XXXXXXXXXXXX This is a snapshot of the Linux kernel tree as of a revision of my choosing. XXXXXXXXXXXX 適用対 象を選択する際に筆者は、当該リビジョンのカーネルリポジトリにおけるチェンジセット ID を、コミットメッセー ジ3 中に記 録しておきます。カーネルツリー中の開発に関連する部位に関して、スナップショットによって “状況” と内容が特定できるため、縮小 版リポジトリと通常版のカーネルツリーのいずれに対しても、パッチの適用が可能になります。 XXXXXX Since the snapshot preserves the “shape” and content of the relevant parts of the kernel tree, I can apply my patches on top of either my tiny repository or a normal kernel tree.
通常は、パッチの適用対象となるソースツリーのベースには、上流リポジトリの直近のスナップショットを使用すべきです。そうすることで、作成したパッチを上流リポジトリの担当者へ送付する際に、殆ど(あるいは全く)改変の必要が無くなるでしょ う。
筆者は、series に列挙されるパッチを、幾つかの論理的なまとまりに分類しています。それぞれのパッチ分類は、その後に列挙される パッチの意図を記述したコメントブロックで開始されます。
筆者の扱っているパッチ分類は、以下のような並びになっています。分類の順序は重要なので、分類を紹介した後で説明しま す。
ではここで、パッチ分類尾をこの順番にする理由に戻りましょう。コンテキストの変更が発生することで、スタック上方のパッチへの再 作業5 が必 要になることが無いように、スタック中で底にあるパッチほど安定していて欲しいものです。変更されにくいパッチ群をseries ファイ ルの冒頭に置くことで、この目的を達成することができます。
他のパッチの適用を極力上流リポジトリの状態に近いソースツリーへ行うために、ソースツリーの変換に必要と思われるパッチも重 要です。受理済みのパッチも暫くの間保持しているのはそのためです。
“バックポート” および “内部用” パッチは、series 末尾近辺を転々とします。バックポートパッチは他の全てのパッチ適用の上で 適用されなければなりませんし、その上、 “内部用” パッチは不都合が無いように内部に留まり続ける必要がありま す。
筆者の作業の際には、パッチ適用を制御するために複数のガードを使用しています。
これらのガード分類により、最終的にどのようなソースツリーが得られるかを決定する際に、少なからぬ柔軟性を得ることができま す。多くの場合、適切なガードの選択は構築手順の中で自動化されていますが、特別な状況向けにガードの調整を手動で行うことも可能 です。
MQ を使用することで、バックポートパッチの作成は単純な作業となります。旧版のカーネル配下においてもドライバが正常に稼動す るように、旧版のカーネルにおいて提供されていない機能を使用するコードの変更が、バックポートパッチのすべきことの全てで す。
良いバックポートパッチを書く際のゴールは、対象とする旧版カーネル向けに書いたかのように、あなたのコードを変 更するようなパッチにすることです。パッチがでしゃばらない程、理解と保守が容易になります。コード中の大量の #ifdef(条件に応じて適用されるコード片)による “鼠の巣” 化を避けるためにバックポートパッチ群を書くのであれ ば、バージョン依存な #ifdef をパッチに持ち込むべきではありません。バージョン依存な #ifdef を使用する替わ りに、個々のパッチはバージョンに依存しない変更を行うようにして、パッチの適用をガードによって制御すべきで す。
“通常” のパッチと、その適用結果を更に変更するバックポートパッチとを、別個のグループに分離するのには2つの理由がありま す。第1の理由は、これらのパッチが混ざり合った場合に、上流リポジトリの保守担当へのパッチ送付の自動化の際 に、patchbomb 拡張のようなツールを使うことが難しくなるためです。第2の理由は、後続の通常パッチの適用コンテキス ト9 をバッ クポートパッチが混乱させてしまい、通常パッチの適用前に適用されたバックポートパッチ抜きでは、通常パッチを綺麗に適用すること ができなくなってしまうためです。
MQ を利用した実在するプロジェクトで作業をしているのであれば、多くのパッチを蓄積することも難しいことではありません。例え ば、筆者は 250 を超えるパッチを抱えたパッチリポジトリを持っています。
パッチを個別の論理的なまとまりに分類できるのであれば、 MQ はパッチ名にパス区切りが 含まれていても問題ないので、それぞれのパッチを異なるディレクトリに格納することもできま す10 。
長期間にわたってパッチの開発を行う場合、12.11 節で述べたように、パッチをリポジトリで管理するのが良いでしょう。その場合は 早々に、パッチの変更履歴の参照に“hg diff” が使えないことに気付くことでしょう。これは実際のコードの二次派生物 (差分の差分) を見ていること以外にも、タイムスタンプやパッチ更新時のディレクトリ名等を改変することで MQ が雑音を加えてしまっていること に原因があります。
Mercurial に同梱されているextdiff 拡張を使うことで、2つの版のパッチ差分を幾分読みやすいものにすることがで きます。この拡張を使うためには、サードパーティーパッケージであるpatchutils [Wau] が必要です。このパッ ケージが提供するinterdiff というコマンドは、差分間の差分を1つの差分として表示します。同じ差分の2つの 版11 に対 してこのコマンドを適用すると、最初の版から次の版へと変更するための差分を生成します。
いつものように、hgrcファイルの[extensions]セクションに行を追加することで、extdiff 拡張を有効化することができま す。
interdiff コマンドは2つのファイル名の指定が必要ですが、extdiff 拡張は、それぞれ任意の数のファイルを配下に持つ、2つ のディレクトリに対して動作するプログラムの指定が必要です。そのため、これら2つのディレクトリ配下の個々のファイル対に対し てinterdiff を実行する小さなプログラムが必要です。本書のソースコードリポジトリにおけるexamples ディレクトリ配下 に、hg-interdiff として格納されています。
hg-interdiff がシェルのコマンド検索パス上に有る場合、 MQ のパッチディレクトリから以下のようにして起動することができ ます。
おそらくこの長たらしいコマンドを何度も使うことになるでしょうから、再度hgrcを編集して、hgext を Mercurial の普通のコマ ンド並に使えるようにしましょう。
この記述により interdiff がhgext から利用できるようになりますので、先の“hg extdiff” 起動も短くなって幾分使いやすく なるでしょう。
extdiff 拡張は、 MQ パッチの表示機能の向上に留まらない有用なものです。extdiff 拡張に関する詳細は、14.2 節を参照してく ださい。