Oasis で楽に OCaml プロジェクトを作る

これは [ML Advent Calendar 2017](https://adventar.org/calendars/2142) の 6日目の記事です．

ご案内： **[Jbuilder (Dune) でもっと楽に OCaml プロジェクトを作る](https://qiita.com/keigoi/items/a68298fcd39322004fed)** もご覧ください。

[Oasis](http://oasis.forge.ocamlcore.org/) は OCaml のビルドツールの一つです． 最低限必要な設定ファイルを作れば，ありがちな依存解析を全て自動的にやってくれて便利です． いわゆる ``./configure && make && make install`` 式のインストーラをうまいこと自動生成してくれます． さらに OCamlfind (Findlib) 周りもよしなにしてくれるので，他の OCamlfind パッケージを使ったり，自前のパッケージの開発も簡単にできます． 

しかし，Oasis の日本語の解説記事はこれといって見当たりません．そこで，この記事では私が Oasis をどのように使っているかを書きます． こまかいことは [Oasis のマニュアル](http://oasis.forge.ocamlcore.org/MANUAL.html) を読んでください． 具体例としては，大きいものだと [Tyxml](https://github.com/ocsigen/tyxml/blob/master/_oasis) があり，他には ["_oasis" で github をググる](https://www.google.co.jp/search?q="_oasis"+github.com%2F+ocaml) などの方法もあります．

# Oasis と OCaml のビルドシステム

Oasis は，他のビルドシステム (OCamlbulid か Omake) を呼び出してプロジェクトをビルドします．Oasis が各ツールのビルドスクリプトを自動生成してくれるのです．

実際，既存のビルドシステムにはそれぞれ一長一短があるように思います：

- make． OCamlマニュアルには，[典型的な Makefile の例](https://caml.inria.fr/pub/docs/manual-ocaml/depend.html#sec334) が掲載されていますが，よほど小さなプロジェクト以外には使えないでしょう． シリアスに Makefile で生きていくなら，よく使う定義をまとめた [OCamlMakefile](https://github.com/mmottl/ocaml-makefile) を使うのが良いかもしれませんが，試したことはない．
- [OCamlbuild](https://github.com/ocaml/ocamlbuild/blob/master/manual/manual.adoc#is-ocamlbuild-well-suited-for-your-project) は OCaml コンパイラについてくるので可搬性が高いですが，ビルド規則を OCaml のソースコード (``myocamlbuild.ml``) に書くのがいかにも複雑な感じです． 
- [Omake](http://omake-japanese.osdn.jp/index.html) は， OCamlプロジェクトに限らず使える柔軟性と汎用性が強みであり，広く使われていますが，私には OMakefile の敷居が高かったです (ヘタレ．一応，[使おうとした形跡](https://github.com/keigoi/coq4ios) はあります)．
- [Jbuilder](https://github.com/janestreet/jbuilder) は Jane Street Capital 発で，今後のデファクトになっていきそうな感じがしますが，S式を書きたくないのと，よく知らないので今回はパスです．

Oasis なら， ``_oasis`` という設定ファイルに最低限のことを書くだけで良いです．

# 使い方

1. ``_oasis`` ファイルを作る (下記)． 
  - ``Library`` (または ``Executable``) セクションを作る
  - ``Modules`` フィールドに最低1つのモジュールを書いておく．
2. ``oasis setup`` 
  - ``Makefile`` や ``./configure``, ``setup.ml`` などのスクリプト一式が生成される．
3. ``make`` (``&& make install``)

簡単ですね．また，``./configure`` (``ocaml setup.ml -configure``) は， 初回に ``make`` を叩いた時に自動で呼ばれます．

注意： 

- プロジェクトにモジュールを追加した時は，``_oasis`` を編集して ``oasis setup`` を呼ぶ
- 何故かビルドが上手くいかなくなったら ``make distclean`` か， ``git clean -fdX`` で要らないファイルを削除して ``oasis setup`` を再実行する．このため ``.gitignore`` も載せておきます．

# _oasis ファイル

OCamlfind で使える典型的なパッケージを作る場合は次のような ``_oasis`` を作ります．実行ファイルを作るときは ``Library`` を ``Executable`` に変えるだけです．複数のライブラリや実行ファイルも書けます．

```
OASISFormat: 0.4
Plugins:     META (0.4), DevFiles (0.4)
BuildTools:  ocamlbuild

Name:        ocamlsample
Version:     0.1
Homepage:    https://github.com/keigoi/ocamlsample
Synopsis:    A sample library
Authors:     Keigo Imai
License:     Apache

Library samplelib
  Path: lib
  Findlibname: samplelib
  BuildDepends: threads, str
  Modules: Sample
```

Oasis プラグイン：

- ``META``プラグイン は OCamlfind の META ファイルを生成
- ``DevFiles`` プラグイン は（``setup.ml`` を呼ぶだけの）スクリプト configure や Makefile を生成

Library セクションについて．

- ``Path`` は，ソースコードを置くディレクトリです．他の Library や Executable とは重複できないようです．
- ``Findlibname`` は，このライブラリを使う時に ``ocamlfind -package パッケージ名`` で指定する時のパッケージ名になります．
- ``BuildDepends`` には依存する OCamlfind のパッケージ名を書きます．
- ``Modules`` は，ライブラリに含めるモジュールのリスト．ファイル名ではなく，モジュール名（``sample.ml`` なら ``Sample`` のように）．
- OCamlfind 向けのオプション： ``FindlibParent`` (親パッケージ), ``XMETAType`` (ppx を作る場合には syntax などと書く)，``XMETARequires`` (他の依存パッケージ), ``XMETAExtraLines`` (META に追加する行. ppx の実行ファイル等を指定) など


テスト (Test) やドキュメント生成 (Document), リポジトリ情報 (SourceRepository) のためのセクションもあります．[マニュアルを見よ](http://oasis.forge.ocamlcore.org/MANUAL.html)．


## インストール

``make install`` で適当な位置にライブラリをインストールしてくれます． Executables はデフォルトでは ``/usr/local/bin`` に入るはず． インストール位置は全て， ``./configure`` に与えるオプションで変えられます．

```
$ make install
ocaml setup.ml -install 
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/sample.ml
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/samplelib.cma
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/samplelib.cmxa
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/samplelib.a
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/samplelib.cmxs
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/sample.cmi
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/sample.cmt
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/sample.annot
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/sample.cmx
Installed /Users/keigoi/.opam/4.05.0+flambda/lib/samplelib/META
```

やったね！


## OMake を使う場合

ビルドシステムとして OMake を使う場合は，``BuildTools: ocamlbuild`` の代わりに，以下のように書けばよいようです．

```
OCamlVersion: >= 4.01
BuildType: OMake (0.4)
BuildTools:  omake
```



# 配布時に必要なファイル

私は，最低限 ``./configure && make && make install`` をできるようにしておきたいので，以下のファイルはリポジトリにコミットしています．

```
Makefile
configure
myocamlbuild.ml (OCamlbuild の場合)
setup.ml

**/META
**/*.mldylib
**/*.mllib
```


# .gitignore

これを書いておいて，何か変なことになったら ``git clean -fdX`` しています．

```
*.annot
*.cmo
*.cma
*.cmi
*.a
*.o
*.cmx
*.cmxs
*.cmxa
*.cmt

# ocamlbuild working directory
_build/

# ocamlbuild targets
*.byte
*.native

# oasis generated files
setup.data
setup.log

# omake files
*.omc
*.om
.omakedb
.omakedb.lock
```

# PPX を伴うライブラリを作る

PPX を提供するライブラリを作るときは次のようにします．

1. Executable でプリプロセッサの実行形式を作る (仮に ``ppx_example`` とする)
2. 提供するライブラリの Library に ``XMETAExtraLines: ppx = "ppx_example"`` を追加する

同じプロジェクトの他のソースコード（テストコード等）で PPX を使いたい場合は、``_tags`` ファイルに

```
# OASIS_START
# OASIS_STOP

<test/*.ml>: ppx_example
```

と書いておいて， ``myocamlbulid.ml`` でなんとかするしかないようです．[例](https://github.com/keigoi/linocaml/blob/6d1f85db46ea6fd951425fd167840318741d3b48/myocamlbuild.ml#L904-L928)


# 気になること

- ``make`` する度に ``ocaml setup.ml -build`` が走るが，このときに 1秒くらい固まる気がする．もっとサクサク動かない？
- 偶に ``make`` が失敗し ``oasis setup`` してもビルドできない状態に陥る．私は何も考えず ``git clean -fdX`` しているが…．

# まとめ

Oasis は， ``_oasis`` から  OCamlbuild の ``myocamlbuild.ml`` や OMake の ``OMakefile``, ``OMakeroot`` を生成してくれる便利ツールで， OCaml のツールチェインに慣れていない人でも使えるのではないかと思います． Oasis で happy OCaml life を！