Dockerでも再現できない環境 / gauche-packageに対応 / Windows対応

というわけでDockerベースのテスト環境をTravisCIで始めた。基本的にはライブラリのbootstrapやFFIライブラリのビルドも含んだ完全なビルド過程の再現を含んでいる。もっとも、まだFFIテストは統合していない(まだ事故ったときにコアダンプとか回収できないし)。

Docker上でも"手元で成功するのにリモートで失敗するテスト"

せっかくDockerにしたのに、一発目から"手元で成功するのにリモートで失敗するテスト"が表われてしまった。ある種の魔物がおるね。。

3/6 Test #3: RACKET-_sanity.sps ...............***Failed    0.78 sec

lib-stub/racket/r7b-util/features.mzscheme.sls:6:4: import: cannot find suitable library installed (exception: collection-path: collection not found

  collection: "nmosh/r7rs"

  in collection directories:

TravisCI上のDockerでは、なぜかRacketがnmosh用の互換ライブラリを見に行ってしまう。当然非Docker環境や手元のDocker(CentOS7上)ではずっとテストはパスしていた。これはDockerのストレージドライバの違いでファイルの列挙順が変わるというやつで、nmosh用のライブラリファイルである*.nmosh.slsをRacketの検索パスに入れてしまっていたため。
というわけでディレクトリを分離し、*.IMPLEMENTATION.slsのコンベンションをツリーでは使わないようにした。ついでにoverrideの順序も見直し。

これはかなり運の良いパターンで、偶然TravisCIと手元でストレージドライバが違っていなかったらこのバグも見つからなかった可能性が高い。環境を増やすのがいかに重要かというポイントでもある。

gauche-packageを呼ぶ

Gauche版のyuniffi対応は専用モジュールが必要なので、ビルド時にgauche-packageを呼ぶように代えた。従来はgauche-packageが生成したファイルをコミットしていたが、gauche側のABIが変わったら終りなので。

...冷静に考えるとこれgauche-packageでインストールまでやった方が良いんだろうか。。Docker環境ではrootなので問題なくinstallできるが、デフォルトのビルドがビルドツリーの外部に影響を与えるのはポリシに反するのでたぶんやらないけど。
ビルドには -I オプションを2つ渡す必要がある(yuniの共通ヘッダとGaucheモジュールの専用ヘッダ)が、gauche-packageが一つしか渡せないので--cppflagsと--cflagsに分けて与えている。基本的にGaucheがin-place build志向なので、CMakeのようなout-of-treeビルドを標準としたシステムとの食べ合せが悪い。
デフォルトのgauche-packageに.stubをビルドさせると適当な場所に.cを配置してしまうので、

(use gauche.cgen.stub)

(cgen-genstub "/path/to/repos/yuni/..../yuniffilib.stub")

のようなSchemeソースをconfigure時に生成して呼ぶことにした。これを行うことで.cの生成場所を制御することができる。

Alpine Linux(Musl libc)対応

追記: ダメだった。Gaucheは動いたけどテストが失敗する。 https://github.com/okuoku/yunibase/issues/6
Docker用の小さなディストリビューションとして比較的人気のあるAlpine LinuxがMuslを採用しているのでテストとしてはこれを使っている。
なんと現状ビルドに成功しているのはchibi-schemeとchickenだけで、他は諸々の問題が有り上手くいっていない。
Gaucheは最近専用の対応がコミットされているので、backportして試すつもり。

基本的にglibc専用の機能はちゃんとチェックしてから使う必要があり、意外とLinuxのlibc == glibcという仮定を置いていることが多い。

Cygwin64対応とMSYS

追記: See comment。GaucheのようなMSYSをサポートしている実装をCygwinでbootstrapするのは単にビルドシステム上の都合なので該当部分をstrike。
メキメキ起票中。

Cygwin64でchmod効かない問題はなかなか根が深そうで微妙。
意外とCygwin32がまだまだシェア高くどうしようか微妙なところ。もう最近の64bit windowsではCygwin32は動かないも同然なのでCygwin64への移行は重要なのだけど、そもそもCygwinユーザ自体が少いという罠も有り微妙。
Cygwin64はビルドにクソ時間が掛かるというポイントを除けば基本的に問題は無いが、MSYSやネイティブWindowsビルドがさらに微妙で、どうやってbootstrapするのかという問題を解決する必要がある。
GaucheNMoshはbootstrapをPOSIX上でないと行えないため、一旦Cygwinでビルドした上でbootstrapし、その後のconfigure等をMSYSなりCMakeで引き継がないといけない。CMakeビルドの場合は、Cygwin上のCMakeでconfigureしたツリーからネイティブのCMakeを呼ぶ必要があるため問題がさらに複雑になる。。
yunibaseでは、基本的にbootstrapはCygwinで行うものとし、MSYSはGaucheのように本当にbuild prerequisiteな場合にだけ使うことにする。ビルドがCMakeで書かれているSagittariusやNMoshはネイティブCMakeからコンパイラを直接呼べば十分。Gauche等についてもCygwin側にMinGWを導入してCygwinでconfigureするという方法も考えられなくないが、クロスビルドはあまり想定されていないことが多く、通常の人間はたぶんMSYS2の方を好むのでMSYS側を使うことにする。
(たとえばCygwinではネイティブドライブは/cygdrive/c等にマウントされているが、MSYSでは/c/のようにルートに直接マウントされているといった違いがある。基本的に中身はCygwinなので、32bit版を使うとしょっちゅうfork()に失敗するのはMSYSでも同様。ABIには互換がないため、CygwinがPATHにある状況でMSYSのバイナリを呼ぶと失敗する。)