i_sparkling

CMakeによるICUのビルド

ICU(International Component for Unicode)をCMake+VisualStudioでビルドするためのパッチを作成しました.

一応ICUの公式の配布物にはVisualStudioのソリューションファイルが含まれているのですが, DLLとしてビルドするのが前提になっていて,スタティックリンクライブラリを簡単に 作成することができません.

マニュアルにはCygwinを入れてゴニョゴニョせよ,とは書いてますが,それだけのために Cygwinを導入するのも大変なので,前述のソリューションファイルやプロジェクト ファイルを分析するrubyスクリプトを書いてCMakeのためのファイルを生成することにしました (使いはしないと思いますがスクリプトごとパッチに入れてあります).

ICUのビルド構造

以下はICUの構造に関する個人的なメモです.

ライブラリと,そのライブラリを構成するソースファイルの関係は.vcxprojから比較的簡単に抜き出すことができますが, ICUのビルド時の構造のため少し複雑な部分があります.

ICUはいくつかのライブラリで構成されています.

ライブラリ名 .vcxproj名 役割
icudt stubdata/makedata 処理に使うテーブルデータ
icuuc common 共通ライブラリ
icuin i18n 日付時刻関連のライブラリ
icuio io 入出力のためのライブラリ
icule layout 複雑な言語での文字表示のためのレイアウトエンジン
iculx layoutex 同上

ややこしいのがicudtというライブラリで,これはICU内部で使用する様々な変換テーブルなどのデータを含んでいるのですが, それらのデータはICUのツール(pkgdataなど)でバイナリファイルとして直接作成されます

そのICUのツールは当然上記のライブラリに依存しているわけで,要するにビルド順序に循環参照が発生しているわけです.

ICU Depends

ビルド時に小さなツールを最初に作ってそれがデータを含むソースファイルを生成する,というライブラリは結構あるのですが, データを含むバイナリを直接生成する,という構成を取っているライブラリは珍しいと思います(といってもpkgdataは.objを作ったのちlibを起動しているだけのようですが).

この循環参照を解決するために,空のデータを生成しているのがstubdataというプロジェクトです. このプロジェクトが仮のicudtライブラリを作ります.これを使ってICUのツール群をいったん作成し, それらが揃った段階でmakedataというプロジェクトが本物のicudtライブラリを作成します.

ICU Depends

makedataプロジェクトは実態としてはMakefile(nmake)です.中身を精査してみましたが,このMakefileが 実際に使用するのはpkgdataとicupkgというツールのみのようです.他のツールを使った生成規則も含まれていますが, 配布されているソースアーカイブ(icu4c-*-src.zip)からビルドする分にはこれらの二つのツールで十分なようです.

(icu4c-*-src.zip内にはicu/source/data/in/icudt*l.datというファイルが含まれていますが,他の生成規則はこれを生成するためのもののようです.別の配布物であるicu4c-*-data.zipにはこの.datファイルを生成するための依存ファイルが含まれていました.)

pkgdataの謎挙動

前述の通りpkgdataツールが,libファイルやdllを生成します.出力ファイル名は,-Lオプションで指定することができるのですが 文字列icudtを含むファイル名を指定すると出力ファイル名が強制的に変更されます.

スタティックモード(-m static)の場合
出力ファイル名にicudtが含まれる場合,指定した文字列の先頭に強制的にsが付く.
DLLモードの場合(-m dll)の場合
出力ファイル名にicudtが含まれる場合,出力ファイル名(インポートライブラリ名)が強制的にicudt.libになる.

windows版のpkgdataとlinux版のpkgdata(Debianだとicu-devtoolsに含まれる)はインターフェースが異なるらしく, このあたりの詳しいことはドキュメント化されていないようです(あるならだれかおせーて).

ソースを見る限り上記のような処理をしていました.

スタティックモードの挙動はicu/source/tools/pkgdata/pkgdata.cppの1772行目付近(pkg_createWindowsDLL関数内)

        sprintf(staticLibFilePath, "%s%s%s%s%s",
                o->targetDir,
                PKGDATA_FILE_SEP_STRING,
                (strstr(o->libName, "icudt") ? "s" : ""),
                o->libName,
                LIB_EXT);

DLLモードは同ファイルの1805行目

        if (strstr(o->libName, "icudt")) {
           uprv_strcat(libFilePath, LIB_FILE);
        } else {
            uprv_strcat(libFilePath, o->libName);
            uprv_strcat(libFilePath, ".lib");
        }
        uprv_strcat(dllFilePath, o->entryName);

なぜそうなっているのかはよくわかりません.
このページに掲載したパッチではこれらをコメントアウトして-Lオプションに指定した値が無条件で反映されるように 修正しています.

あとDLLモードではdllファイルのファイル名を制御する方法がないようです.-pオプション(.obj内のシンボル名のプレフィックス)に 指定した値に.dllをつけたファイル名になるようです.

Leave a Reply