ActivePerlでXML::LibXSLTを使う

IRC経由で頼まれたのでXML::LibXSLTのパッケージングをしてみたのですが、いろいろハマりどころが多かったのでまとめておきます。
1) libxsltは自前でコンパイルする必要あり
ふつうならhttp://xmlsoft.org/downloads.htmlから入手できるコンパイル済みのバイナリを使えばよさそうなものですが、Windowsの場合ファイル名の大文字小文字が無視されてしまう都合でXML::LibXSLTから生成されるLibXSLT.dllとコンパイル済みのlibxslt.dllが衝突するので、ソースを落としてMakefileをこんな風に修正してからコンパイルする必要がある。

diff -ur libxslt-1.1.21/win32/Makefile.msvc libxslt-1.1.21-patched/win32/Makefile.msvc
--- libxslt-1.1.21/win32/Makefile.msvc	2007-01-04 00:11:57.000000000 +0900
+++ libxslt-1.1.21-patched/win32/Makefile.msvc	2007-08-07 05:31:17.466250000 +0900
@@ -26,13 +26,13 @@
 
 # Names of various input and output components.
 XSLT_NAME = xslt
-XSLT_BASENAME = lib$(XSLT_NAME)
+XSLT_BASENAME = $(XSLT_NAME)
 XSLT_SO = $(XSLT_BASENAME).dll
 XSLT_IMP = $(XSLT_BASENAME).lib
 XSLT_DEF = $(XSLT_BASENAME).def
 XSLT_A = $(XSLT_BASENAME)_a.lib
 EXSLT_NAME = exslt
-EXSLT_BASENAME = lib$(EXSLT_NAME)
+EXSLT_BASENAME = $(EXSLT_NAME)
 EXSLT_SO = $(EXSLT_BASENAME).dll
 EXSLT_IMP = $(EXSLT_BASENAME).lib
 EXSLT_DEF = $(EXSLT_BASENAME).def

2) 少なくともMSVCを使う場合はS_ISDIRマクロの定義が必要
XSLTを使ってディレクトリ階層付きのファイルを生成することがなければ無視してもかまいませんが、MSVCにはS_ISDIRというマクロが登録されていないため、そのままコンパイルすると常にディレクトリの判定に失敗してしまい、必要なディレクトリを掘ってくれなくなります。丁寧に書くならさらに_MSC_VERでくるんだ方がよいかもしれませんが、とりあえずこんなパッチで対応可能。

diff -ur libxslt-1.1.21/libxslt/win32config.h libxslt-1.1.21-patched/libxslt/win32config.h
--- libxslt-1.1.21/libxslt/win32config.h	2007-01-04 00:11:57.000000000 +0900
+++ libxslt-1.1.21-patched/libxslt/win32config.h	2007-08-07 10:59:04.457500000 +0900
@@ -93,5 +93,9 @@
 #define ATTRIBUTE_UNUSED
 #endif
 
+#ifndef S_ISDIR
+# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+#endif
+
 #endif /* __LIBXSLT_WIN32_CONFIG__ */
 

3) ディレクトリを掘るときにはlibxml2の修正も必要
どうするのが一番よいのかはわかりませんが、現行のlibxml2のWindows版はディレクトリの判定が「\」固定なのに、URL/ファイル名のパース時には「\」に対応してくれていないようなので、とりあえずこんな感じのパッチをあてる必要あり。Win環境であろうとどうせファイル名に「/」を使うことはできないので、両対応させておけばいいや、と。

diff -ur libxml2-2.6.29/xmlIO.c libxml2-2.6.29-patched/xmlIO.c
--- libxml2-2.6.29/xmlIO.c	2007-06-12 18:36:35.000000000 +0900
+++ libxml2-2.6.29-patched/xmlIO.c	2007-08-07 12:09:11.060625000 +0900
@@ -3494,6 +3494,7 @@
     char dir[1024];
     char *cur;
     char sep = '/';
+    char sep_win = '\\';
 
 #ifdef _WIN32_WCE  /* easy way by now ... wince does not have dirs! */
     return NULL;
@@ -3503,18 +3504,15 @@
 	xmlRegisterDefaultInputCallbacks();
 
     if (filename == NULL) return(NULL);
-#if defined(WIN32) && !defined(__CYGWIN__)
-    sep = '\\';
-#endif
 
     strncpy(dir, filename, 1023);
     dir[1023] = 0;
     cur = &dir[strlen(dir)];
     while (cur > dir) {
-         if (*cur == sep) break;
+         if (*cur == sep || *cur == sep_win) break;
 	 cur --;
     }
-    if (*cur == sep) {
+    if (*cur == sep || *cur == sep_win) {
         if (cur == dir) dir[1] = 0;
 	else *cur = 0;
 	ret = xmlMemStrdup(dir);

4) MSVCだとtestapi.cをうまく対処できない
文字コードの都合とかあるのかもしれませんが、ともあれそのままコンパイルしようとすると怒られるので、適当に対処(これでlibxml2側のテストが正しく通るのかは未確認)。

diff -ur libxml2-2.6.29/testapi.c libxml2-2.6.29-patched/testapi.c
--- libxml2-2.6.29/testapi.c	2007-05-28 23:08:52.000000000 +0900
+++ libxml2-2.6.29-patched/testapi.c	2007-08-07 07:55:15.847500000 +0900
@@ -291,7 +291,7 @@
 static xmlChar gen_xmlChar(int no, int nr ATTRIBUTE_UNUSED) {
     if (no == 0) return('a');
     if (no == 1) return(' ');
-    if (no == 2) return((xmlChar) '・);
+    if (no == 2) return((xmlChar) '・');
     return(0);
 }

iconvとzlibの用意もできたら、libxml2とxslt/exsltをコンパイル。INCとかPATHとか通っている場所にインストールしておきます。

5) MSVCを使うならXML::LibXSLTのMakefile.PLも修正
gccでは試していませんが、とりあえずMSVCの場合、-lzの展開とクォーテーションの種類の問題で不都合が出るので、こんな感じのパッチを用意。コンパイルテストをすっ飛ばしているのは単なる横着のためです。

--- XML-LibXSLT-1.62/Makefile.PL	2006-08-27 02:49:04.000000000 +0900
+++ XML-LibXSLT-1.62-patched/Makefile.PL	2007-08-07 10:46:58.254375000 +0900
@@ -60,10 +60,10 @@
 }
 
 if ($config{LIBS} !~ /\-lxslt/) {
-    $config{LIBS} .= ' -lxslt -lxml2 -lz -lm';
+    $config{LIBS} .= ' -lxslt -lxml2 -lzlib';
 }
 
-if (!have_library("xslt")) {
+if (0 && !have_library("xslt")) {
     die <<DEATH;
 libxslt not found
 Try setting LIBS and INC values on the command line
@@ -74,7 +74,7 @@
 DEATH
 }
 
-if (have_library("exslt")) {
+if (1 or have_library("exslt")) {
     $config{LIBS} =~ s/-lxslt/-lxslt -lexslt/;
     $config{DEFINE} .= " -DHAVE_EXSLT"
 }
@@ -253,9 +253,9 @@
 use Conftest; \$loaded++;
 EOT
     close($cfile);
-    xsystem("$^X Makefile.PL " . join(' ', map { "'$_=$config{$_}'" } keys %config));
+    xsystem("$^X Makefile.PL " . join(' ', map { qq{"$_=$config{$_}"} } keys %config));
     my $file = $config{MAKEAPERL} ? "-f Makefile.aperl FIRST_MAKEFILE=Makefile.aperl" : "";
-    xsystem("$Config{make} $file test 'OTHERLDFLAGS=$opt'");
+    xsystem(qq{$Config{make} $file test "OTHERLDFLAGS=$opt"});
 }
 
 sub try_link {

正しく環境構築できていれば、これでXML::LibXSLTのテストも通るはず。

どうせなのでXML::LibXML側も修正しておこうと思ったのですが、

6) 最新版のlibxml2(2.6.29)を使うとXML::LibXMLのテストがこける
詳しくは http://rt.cpan.org/Public/Bug/Display.html?id=27659 参照。バージョンをひとつ落としてコンパイルし直すことも考えたのですが、RTの話の流れからするとそこまでする必要もなさそうだったので、こけたテストは無視してあります。

というわけで、うちのリポからインストールできるXML::LibXMLは最新版でコンパイルし直しました。変更点についてはオフィシャルサイトを見ていただくとして、必要と思われる方は -force を使うなりなんなりしてXML::LibXML(と各種ライブラリ)を強制的に更新してください。不具合が出るようでしたらご一報いただけるとありがたいです。