• OPAMはバージョン2を使いましょう
  • OPAMを使うと複数のOCamlコンパイラバージョンを簡単に使いわけられる
  • ディレクトリスイッチを使うとカレントディレクトリによって自動的にスイッチできる

複数のバージョンのOCamlシステムをインストールする

複数のOCamlコンパイラバージョンを使って仕事をすることはよくあります。 普段は最新バージョンのOCamlコンパイラを使用していたとしても、例えば、依存ライブラリがまだ最新のOCamlバージョンに対応していないアプリ開発の場合には古いバージョンのコンパイラが欲しくなります。また、コンパイラバージョンによって細かい修正が必要なライブラリを開発する際にはいくつものバージョンのコンパイラが手元に必要になります。一つ一つ異なるバージョンのOCamlコンパイラを別ディレクトリにイストールしていくことは可能ですが、面倒です。

OPAMにはスイッチという概念があって、スイッチによって複数のバージョンのOCamlコンパイラ、例えば、4.06.1と4.07.0を同時に持つことができ、簡単に切り替えられます。

実際にやってみましょう。(バージョン2のOPAMがちゃんとインストールされている事を仮定しています。バージョン1の人はアップグレードしてくださいね。) opam switch create <バージョン>はコンパイラを一からインストールするので、わりと時間がかかります:

$ opam switch create 4.06.1

<><> Gathering sources <><><><><><><><><><><><><><><><><><><><><><><><>  🐫 
...
Done.
$ opam switch create 4.07.0

<><> Gathering sources <><><><><><><><><><><><><><><><><><><><><><><><>  🐫 
...
Done.

実際にインストールされたスイッチ一覧をopam switchで見てみましょう:

$ opam switch
#  switch  compiler                    description
   4.06.1  ocaml-base-compiler.4.06.1  4.06.1
→  4.07.0  ocaml-base-compiler.4.07.0  4.07.0

後にインストールされた方のバージョン4.07.0のスイッチが選択された状態です。 シェルのパスもそれに併せて変更されているので、バージョン4.07.0のOCamlコンパイラやツールが選ばれます。確認してみましょう:

$ which ocamlc
/home/dl/.opam/4.07.0/bin/ocamlc
$ ocamlc -version
4.07.0

使用するコンパイラスイッチを選択する

使用するコンパイラスイッチを変更したくなったらopam switchで「スイッチ」します:

$ opam switch 4.06.1

これだけです。これでバージョン4.06.0のOCamlコンパイラやツールが選ばれるようになりました:

$ which ocamlc
/home/dl/.opam/4.06.1/bin/ocamlc
$ ocamlc -version
4.06.1

あれ?ちょっと待って。パスが勝手に変わったみたいです。どうしてこうなった?

$ echo $PATH
/home/dl/.opam/4.06.1/bin:/home/dl/bin:...:/usr/X11R6/bin:/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin

Unixでは、親プロセスの環境変数は子プロセスからは変更できません。しかし、opam switchするとopamコマンドのプロセスが親であるシェルの環境変数PATHを変更している、ように見えます。なぜだろう。

実はこれはOPAM(バージョン2)のハックで、OPAMインストール時に設定されたシェルのフックのおかげです。$HOME/.opam/opam-init/env_hook.*に各シェルのフック定義があります。例えばBashだとこんな風になっている:

$ cat $HOME/.opam/opam-init/env_hook.sh
OPAMNOENVNOTICE=true; export OPAMNOENVNOTICE;
_opam_env_hook() {
 local previous_exit_status=$?;
 eval $(opam env --shell=bash --readonly);
 return $previous_exit_status;
};
if ! [[ "$PROMPT_COMMAND" =~ _opam_env_hook ]]; then
    PROMPT_COMMAND="_opam_env_hook;$PROMPT_COMMAND";
fi

Bashは、PROMPT_COMMANDという変数が何か値を持っていると、プロンプトを出す前にこの変数の内容をコマンドとして実行します。それを利用して各プロンプトごとに環境変数をアップデートしています。

ディレクトリスイッチを使って自動的にスイッチする

複数のコンパイラバージョンはopam switchコマンドによって切り替えられることがわかりました。 が、実は複数のバージョンの間を行ったり来たりする時、opam switchをいちいちやるのは面倒ですし、すぐ忘れてしまいます。あるOCamlコンパイラバージョンのスイッチで仕事をしていたディレクトリで、間違って別のバージョンのスイッチを使ってしまうと、二つのコンパイラが生成した非互換なオブジェクトファイル(*.cm*)がディレクトリに混在してしまいビルドに失敗してしまうことがあります。こうなってしまうと、生成ファイルを一旦全て消去し、正しいコンパイラバージョンを選び直してから全てコンパイルしなおす、くらいしか確実な回復法がありません。

このコンパイラバージョンの誤使用を避けるために、OPAM2ではシェルのカレントディレクトリによって自動的にスイッチするディレクトリスイッチ機能があります。(実現には上記のシェルフックを使っています。)これを使うと、普段はバージョン4.07.0を使っていても、バージョン4.06.1を使うと決めたディレクトリに移っただけで、自動的にバージョン4.06.1にスイッチします。とても便利。

古めのOCamlコンパイラバージョン4.06.1を使うディレクトリスイッチoldを作ってみましょう。 ./oldとしてディレクトリスイッチであることをOPAMに明示することを忘れずに。忘れてoldとすると単にoldという名前のディレクトリスイッチではない普通のスイッチが作られてしまいます。コンパイラ一式をインストールしなければいけないので、また時間がかかります:

$ opam switch create ./old 4.06.1

<><> Gathering sources <><><><><><><><><><><><><><><><><><><><><><><><>  🐫 
...
Done.

./old/_opamというディレクトリができていることを確認してください。 この中に、このディレクトリスイッチの関連ファイル全てががインストールされます:

$ ls -F old
_opam/

さて、このoldディレクトリの外では元のスイッチが使われますが、 いったん、cdでカレントディレクトリをoldの中に移動すると、 自動的にスイッチが切り替わります:

まず、oldの外で4.07.0のスイッチに切り替えます:

$ pwd
/home/dl
$ opam switch 4.07.0
$ which ocamlc
/home/dl/.opam/4.07.0/bin/ocamlc
$ ocamlc -version
4.07.0

oldの中に入ってみると自動的にコンパイラの呼び出しが4.06.1に切り替わります:

$ cd ./old
$ which ocamlc
/home/dl/old/_opam/bin/ocamlc
$ ocamlc -version
4.06.1

oldディレクトリを出るとまた元のスイッチに戻ります:

$ cd ..
$ which ocamlc
/home/dl/.opam/4.07.0/bin/ocamlc
$ ocamlc -version
4.07.0

プロンプトが使えない状況ではopam execが便利

シェルのプロンプトフックが使える状況ではディレクトリによって自動スイッチ切り替えができますが、そうでない場合は自動スイッチが使えません。そういった場合は、opam exec <コマンド>が便利。対応するスイッチへの環境変数を設定した上でコマンドを呼び出してくれます。

Emacs内からMerlinを使う場合、このopam execを使っているので、複数のスイッチ上のOCamlコードを一つのEmacsから扱っていても、自動的に対応したMerlinバイナリを呼び出してシームレスにIDE開発ができます。(他のエディタからも同様ではないでしょうか。)