yuniの設計とCI戦略

yuniのようなマルチ処理系なライブラリは開発戦略がなかなか難しい。C言語ライブラリをVisual Studiogccの両方に対応させるようなもので、各種Scheme処理系をどうやって並べ、どうやってCIを回すかという点に考察が必要になる。
... 幸い、Scheme処理系は大体が自由に配布可能なライセンスを採用しているため、イメージファイルの配布という形で問題をかなりシンプルにできる。
(これがC/C++になると、Visual StudioXCodeのような自由に配布できない処理系の存在が問題をより複雑にする -- 実際にはTravis CIのような外部サービスで解決することになる。外部サービスは管理に制約があるため、例えば、バージョン管理等の問題が起こる。)

設計: 相互運用性

yuniのような後発のライブラリにとっては"相互運用性"は非常に重要な課題となる。ライブラリが使える場所を可能な限り多くすることが重要な営業上の戦略であり、使われるチャンスを得るための必要条件とも言える。
相互運用性にはいくつかの種類が有る:
処理系自身との統合。yuniの提供するライブラリと処理系ネイティブのライブラリを混在させたプログラミングをサポートする必要がある。おそらくyuniの利用の殆どはFFIバインディングを単に使うためのものとなるため、このユースケースを最も熱心にサポートする必要がある。
この混在の要件は意外と自明でなく、Racket-r7rsの長いIssue https://github.com/lexi-lambda/racket-r7rs/issues/3 でも問題になっている。ちなみに、Racket上のyuniでは全てR6RSの挙動に合わせている -- R6RSベースで開発することにより、Racketにおける標準のimmutable-pairではなく、mutable-pairを全体で使用し、必要に応じて明示的にオブジェクトを変換している:

(library (racket-yuni compat ffi primitives)
         (export ...)
         (import (yuni scheme)
                 (rename
                   (only (racket base)
                         map
                         list->vector
                         path->string
                         current-library-collection-paths)
                   (map map:racket)
                   (list->vector list->vector:racket))
                   ...)

(define (module-path)
  (vector->list
    (list->vector:racket
      (map:racket path->string
                  (current-library-collection-paths)))))
)

ここで、Racketのライブラリ current-library-collection-paths はimmutableなlistを返すため、mutableなlistを取るR6RSのmapは使用できない。ここではRacketネイティブのmapを追加でimport、renameして使用している。
(ちなみに、このIssueではJohn Cowan -- R7RS編者の1人でもある -- は逆を推奨している。

I recommend that you resolve this by using Racket pairs instead of mpairs, and simply noting that set-car! and set-cdr! are not supported in #lang r7rs. It's not perfect from a portability perspective, but it clearly provides better interaction between R7RS and the rest of the Racket libraries.

つまり、immutable-pairをR7RSのpairとしても見せることで、変換を挟まずにRacketの手続きを使用できるようにする方向となる。)
処理系間でのプログラム移植性。"FFIライブラリをwrapし、よりSchemeらしく使用できるようにするライブラリ"がyuniの次に期待されるユースケースとなる。このため、yuniのライブラリだけで書かれたプログラムは常にyuniをサポートしたどの処理系でも動作するようにライブラリを設計する必要がある。例えば、bignumを使用しない、yuniのライブラリを書く場合は#vu8(...)のようなbytevectorリテラルや [ ] のカッコを使わない等。
この目的のため、yuniは"ライブラリの使用して良い = 互換性界面となるScheme手続きのセット"を(yuni scheme)ライブラリとして提供している。(yuni scheme)ライブラリは、基本的にR7RSの挙動に揃えたScheme手続および構文の集合となっており、R7RSでのパフォーマンスに配慮している。互換性界面となるライブラリは(yuni ...)のようなyuniで始まるライブラリ名を持たせることで識別可能にしている。
UXの統一。直前のエントリ http://d.hatena.ne.jp/mjt/20160831/p1 のように、異なる処理系でも可能な限り同じUXを得られるように配慮する。この点に関してはあまり考察は進んでいないが、ライブラリ開発者に他の処理系でのテストを行わせるための方法として非常に重要なものと言える。

CI戦略

先のRacket-R7RSのIssueでもJohn Cowanが言及しているように、

Importing arbitrary portable R7RS libraries to the Racket platform with minimum friction. Granted, there aren't very many such libraries yet, but one of the principal purposes of R7RS-small was to make it easy to write them and allow existing implementations to import them.

R7RSはそもそもライブラリの相互運用性を重要な目標に据えて設計されている。が、現実にはそれなりのglueやworkaroundがyuniにおいても必要であり、実際の処理系を使ってCIをしなければ必要な品質のライブラリを提供することは不可能と言える。
yuniのCI環境は2つのリポジトリ -- yuni自身とCIのための環境を含むyunibase -- で構成され、それぞれのリポジトリから3種のイメージが生成される。イメージ類は現状Dockerのみのサポートで、他の選択肢はまだまだ検討中。

yuniリポジトリ( https://github.com/okuoku/yuni )はyuniライブラリ自身と、yuniの実行に必要なScheme処理系のパッチ、個々のScheme処理系に対応したC extensionを収録している。このうち、Scheme処理系のパッチはyunibaseイメージをビルドする際に必要であるため、yuniリポジトリはyunibaseリポジトリにも収録されている。
yunibaseリポジトリ( https://github.com/okuoku/yunibase )はyuniの開発環境をビルドするために必要なスクリプトScheme処理系のソースコードを収録したリポジトリで、yuniリポジトリとは独立して管理されている。yuni自体の開発をする上ではyunibaseリポジトリは不要となるように分割している。
yunibuildイメージ( https://microbadger.com/#/images/okuoku/yunibuild )は、yunibaseをビルドするための環境となるイメージで、MicroBadgerの出力に有るように、単にubuntuであれば必要なパッケージをapt-getしただけのイメージとなっている。ビルドに必要なイメージを分割することで、yunibaseリポジトリの更新の度にビルド用イメージを再生成せずに済ませている。(また、BoehmGCが32bit Dockerイメージで使用できない問題 のような環境問題が発生した場合の切り分けにも役立つ。)
yunibaseイメージ( https://microbadger.com/#/images/okuoku/yunibase:testing )は、yunibuildイメージ上でyunibaseリポジトリをビルドしたもので、各種Scheme処理系をインストールしただけのイメージとなっている。Scheme処理系は/usr以下ではなく専用のディレクトリに分けてインストールされる。yuniライブラリのテストのためのイメージなので、yuniライブラリはインストールされていない。
yuniイメージ( https://microbadger.com/#/images/okuoku/yunibase:latest )は、yunibaseイメージにyuniライブラリをセットアップしたイメージで、yuni周辺ライブラリの開発者が使用することを期待している。
ユーザと配布の関係を纏めると:

  • yuniを利用したアプリケーションの利用者および開発者は、処理系と前回( http://d.hatena.ne.jp/mjt/20160831/p1 )書いたようなランチャさえ使用すれば良いため、これらのイメージを気にする必要は無いし、yuniリポジトリをチェックアウトする必要もないようにしたい。
  • yuni自身の開発者とyuniアプリケーションから使用されるライブラリの開発者は、yunibaseイメージをダウンロードし、かつ、yuniリポジトリを使用する。
  • yuniを利用したアプリケーションのCIは、yuniイメージを使用することになる。Travis CI等大体のCIシステムはDockerイメージをサポートしている。
  • yuni自体のCIにはyunibaseイメージとyuniリポジトリを使用する。

ちなみにyunibuild/yunibase/yuniの各イメージは生成に時間が掛かる(数時間程度)ためパブリックなCIシステムでは生成していない。各種イメージを生成するために掛かるコストはyunibaseイメージが大半を占めており、AzureやAWS上で生成させると1回あたり50円程度 + トラフィックとなる。流石に各種Schemeの全リビジョンをビルドしていると運営コストが問題になるので、安くつくる方法を模索できるまではローカルのマシンでビルドを行う。