yunomuのブログ

酒とゲームと上から目線

パッケージ管理システムのこと

 自分で作ったプログラムを実行することは、ほとんどの場合で簡単にできます。だいたい動作確認しながら作るので当たり前です。
 でも自分で作ったプログラムを人に渡して使ってもらおうとすると、これが結構面倒くさい。
 ここでプログラムというのは、実行ファイルというかOSのロードモジュールの事を指すとします。

 まず、渡した相手のコンピュータのCPUが違うと動かない。PowerPCやARM用のプログラムはIntelアーキテクチャのプロセッサでは動かない。
 動的リンクライブラリを使っている場合は、動的リンクライブラリのパスが違うと動かない。
 そもそも依存しているライブラリがインストールされていないと動かない。
 設定ファイルを使う場合はパスやファイル名が違うと動かない。
 プログラムファイルの形式が、OSのローダが対応しているものでないと動かない。ELFなのかMach-OなのかPortable Executableなのか。
 OSの機能を呼び出すなら、呼び出し方が合っていないといけないし、呼び出す機能が存在しないといけない。
 デバイスを使っている場合は、デバイスの指定方法やアクセス方法が違うと動かない。まあこの辺はだいたいOSに任せるけども。
 などなど、結構面倒くさい。

 実のところこれらの面倒くさいあれこれは、プログラムをソースコードのまま渡すことである程度解決します。
 通常、コンパイラは自分の環境で動くプログラムを出力するので、CPUが違って動かないとか、プログラムの形式が違って動かないとかいう事は起きません。
 動的リンクライブラリにしても、パスはコンパイル時(正確にはリンク時)にプログラムに埋め込んでくれるので、インストールしてさえあればおおむね大丈夫です。
 自分の環境で動かないという事があれば、なんだったらソースコードを直してもいい。ヘッダファイルに環境依存情報(ファイルパスとかメモリ量とか)が書いてあって、そこをちょっと直すと動くという事は、よくあるというかあったというかあったらしいというか想像に難くない。

 そういう環境依存でのコンパイル段階でのトラブルを避けるために、ヘッダファイルやマクロを使って色んな環境でコンパイルできるように工夫します。例えば、

#if VERSION <= 5
#include "env5.h"
#endif

などとやると、VERSIONマクロが5以下の時だけenv5.hというヘッダファイルが読み込まれます。
 こういう風に、OSが違ったら、バージョンが違ったら、ライブラリがインストールされてなかったら、int型のバイト数が違ったら……と、様々な状況を想定してコードを書きます。
 場合によっては、環境に依存したヘッダファイルを生成するためのプログラムを作って、コンパイル前に実行したりします。OSやコンパイラコンパイルではたぶん必ずやっています。

 そうなってくると、コンパイル時に色んな事を考えないといけなくなります。どういう手順でコンパイルすればいいのか、マクロ変数の値をなんにしたらいいか、ヘッダファイルはどれを使えばいいのか、そもそもどのファイルをコンパイルしてリンクすればいいのか。
 いろいろあって面倒なので、たいていそういう情報はMakefileに書いてあります。というかこれくらい色々あると開発中でも面倒なので、そういうコンパイル手順のアレコレはだいたいMakefileに書いてあります。なので、

% gcc -o program src1.c src2.c src3.c -Iinclude -L/usr/local/lib -DARCH=I386

みたいにやっていたのが、

% make

とかで一発で実行ファイルができあがる。便利!

 ところが、まだまだそううまくいかない。
 Makefileには、プログラムのコンパイルの手順が書いてあるんですが、中身は単にコマンドの羅列なので、コマンドが無かったり名前が違ったりすると動きません。ファイルを移動するmvコマンドがmoveだったり、whichが無かったり、ライブラリが変な場所にインストールされていたりするとコンパイルできなかったりします。
 さらに、仮にプログラムがコンパイルできたとしても、動的リンクライブラリが無かったりパスを間違えていたりすると実行できません。

 これを解決するのがautoconfだったりconfigureスクリプトです。
 よくプログラムをソースからインストールする時に

% ./configure
% make
% make install

みたいな事をすると思いますが、この"./configure"というやつ。これを実行すると、コンパイルに必要なライブラリやコマンドやヘッダファイルがインストールされているかをチェックして、環境に適したMakefileを生成してくれます。
 configureが無い場合はまたそれを生成するツールなんかもあったりします。ここらへんは、だいたいREADMEとかMakefileの頭のコメントとか適当に実行した時のエラーメッセージに書いてあるし、慣れればファイル構成を見ただけでどうすればいいかわかるようになります。

 これでもうソースコードで公開されているソフトウェアの大半はコンパイルして使う事ができるようになる気がします。が、そもそもそのソースコードはどこで配布されてるのか。まあ今だったらググれば大抵公式サイトが出てくるのでそこから取ってくればよろしい。でも公式サイトがわからなかったり、検索エンジンが無い時代だったりすると、探すのが大変だったり、人に教えるのも大変だったり。
 バージョンアップされた時も、更新した情報をどこからか入手して、ソースコードを入手して、コンパイルする必要があって、結構面倒です。

 そこで、公開されている便利なプログラムの一覧を管理するシステムができます。とりあえず私が知ってるのはFreeBSDportsなのでportsの話をします。
 portsは、プログラムのリストを持っていて、例えばemacsなら/usr/ports/editor/emacs/とか、curlなら/usr/ports/net/curl/という風に、カテゴリごとに分けたディレクトリに情報が保存されています。いや正確なパスなどは忘れましたが。これで、例えば

% cd /usr/ports/editor/emacs
% make && make install

みたいにやるとめでたくemacsがインストールされます。

 例えばこの場合の/usr/ports/editor/emacsにあるMakefileが何をやっているかというと、emacsソースコードを配布しているサイトからソースコードと、場合によっては修正パッチが入ったファイルをダウンロードしてきて、解凍してパッチを当ててコンパイルしている。ソースコードがどこで配布されているかとか、パッチはどれかとか、そういうのは全部このMakefileやそれが呼び出すシェルスクリプトなんかに書いてあって、自動的に処理してくれる。といってもまあそういうのもパッケージの管理者が書いていてくれているんですが。

 で、バージョンアップの時はどうやっているのかというと、まあバージョンアップがあった事を知るのはやっぱりこまめな情報収集が必要だったりするんですけども、このportsのディレクトリ構成自体がCVSとかを使ってどこかのマスターデータと同期するようになっていて、誰かがソフトウェアを更新するとパッケージ管理者がportsのマスターデータを修正して、利用者はデータ同期してその情報を持ってきて、あとは最初と同じように"make && make install"で更新するというか上書きする。
 非常にシンプルで想像しやすい。

 ただ、portsは基本的にはソースコードからインストールする構造になっているので、インストールに結構時間がかかる。
 もちろん最初の方で書いたように、ソースコードからコンパイルした方が安全ではあるものの、例えば同じOSを使っていてハードウェアもOSで吸収できる程度の違いしか無いような環境だとか、実行ファイルやOSに互換性がある場合なんかは、あるコンピュータ上でコンパイルした実行ファイルがそのまま他のコンピュータにコピーしても動いたりします。
 というかWindowsなんかはそうだし、最近のOSはだいたいそうですね。
 そうなると、わざわざソースコードをダウンロードしてコンパイルするよりも実行ファイルをダウンロードした方が圧倒的に速いのでそうしようとなる。コンパイルは、どこかの似たような環境の誰かがやってくれていればいい。
 ただし、依存する他のプログラムやライブラリなんかが存在しないとやっぱり動かないので、その辺はきちんと把握しておく必要がある。
 ということで、バイナリパッケージというのが生まれた、のだと思います。

 例えばRed Hat LinuxRPMなんかは、パッケージをrpm形式のファイルで管理していて、rpmファイルの中には実行ファイルやライブラリや設定ファイルが入っている。それと、それらのファイルをどこに置けばいいのかとか、あとは依存する外部ライブラリの名前なんかの情報も書かれています。
 rpmファイルをインストールすると、中の情報にしたがって適切にプログラムやファイルを配置してくれて、RPMが持っているデータベースに情報が登録されます。これで現在どんなパッケージがインストールされているかを管理することができるようになっていて、例えば依存するパッケージがインストールされていなかった場合にはエラーを出してインストールが失敗します。
 RPMの場合はこんな感じで、自分でRPMファイルを探してきて、依存するパッケージも自分で探してインストールする必要がありますが、パッケージ管理の基本としてはそんな感じです。

 もちろんそれだと面倒くさいので、Yumみたいなのが登場します。
 Yumは、パッケージ名を指定するとRPMファイルをダウンロードするだけでなく、依存するパッケージがある場合はそれらもインストールしてくれるとても便利なものです。もちろんyumも中央で管理されているリポジトリがあって、それが更新されることでパッケージのバージョンアップにも追随できます。これまでの問題を概ね全部解決できるような素敵なシステムです。
 Debian使ってたからdpkg/APTで説明すればよかった……。まあAPTも同じような感じです。
 YumやAPTのみならず、RubyのgemとかJavaMavenとかHaskellのcabalとかもおおむね同じ仕組になっています。いやcabalはソースからビルドだからportsに近いし、gemもコンパイル無いし、MavenはバイナリだけどJavaだし、仕組みは似てるけど違うか。

 そしてこれより先の事は知らん。

 というのが、私が認識しているパッケージ管理システムの話です。実際のところは、APTあたりからこの世界に入ってきたのでよくわかりません。
 いや、そういえば数ヶ月前にこの辺の事を教えてと言われていたので。
 だからどうってことは無いんですけども、歴史というか、流れをある程度知っていると次の新しいものを理解しやすかったりするし、なんだったら作れる可能性もできるし、面白いんじゃないですかね。

 で、そもそも質問ってなんでしたっけ。