OS X に macports で入れた pear (@ PHP 5.2.10) が使えない現象とそれへの対処
ref: Memo
ref: pear-forum.org
症状
pear や pecl のパッケージをインストールしようとすると、
% sudo pear install -a HTTP_Request pear.php.net is using a unsupported protocal - This should never happen. install failed
という風に、pear コマンドが動作してくれない症状に遭遇した。
環境
OS X 10.5.7 に、以下のような +pear variant 付きの php5 (5.2.10) を macports でインストールしてある。
% sudo port installed | grep php5 php5 @5.2.10_1+apache2+dbase+fastcgi+gmp+imap+ipc+macosx+mssql+mysql5+oracle+pcntl+pear+postgresql83+pspell+readline+snmp+sockets+sqlite+t1lib+tidy (active)
% php --version PHP 5.2.10 (cli) (built: Jun 30 2009 17:44:39) Copyright (c) 1997-2009 The PHP Group Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies
% pear version PEAR Version: 1.8.0 PHP Version: 5.2.10 Zend Engine Version: 2.2.0 Running on: Darwin mac-3.local 9.7.1 Darwin Kernel Version 9.7.1: Thu Apr 23 13:52:18 PDT 2009; root:xnu-1228.14.1~1/RELEASE_I386 i386
対処
フォーラム を元に、以下の作業を行った。
なお、冒頭の ref 先の blog では、PHP 5.2.9 環境のファイルを PHP 5.2.10 へとコピーしているが、筆者の環境では PHP 5.2.10 環境を構築していなかったので、フォーラムにペーストされていたファイルの中身を元に作業をしている。中身の信頼性には要注意である。
まず、以下のようにファイルをバックアップ。
% cd /opt/local/lib/php/.channels % sudo mv pear.php.net.reg pear.php.net.reg.org % sudo mv pecl.php.net.reg pecl.php.net.reg.org
次に、以下の中身のファイルを .channels ディレクトリに作成する。
pear.php.net.reg
a:6:{s:7:"attribs";a:4:{s:7:"version";s:3:"1.0";s:5:"xmlns";s:31:"http://pear.php.net/channel-1.0";s:9:"xmlns:xsi";s:41:"http://www.w3.org/2001/XMLSchema-instance";s:18:"xsi:schemaLocation";s:71:"http://pear.php.net/channel-1.0 http://pear.php.net/dtd/channel-1.0.xsd";}s:4:"name";s:12:"pear.php.net";s:14:"suggestedalias";s:4:"pear";s:7:"summary";s:40:"PHP Extension and Application Repository";s:7:"servers";a:2:{s:7:"primary";a:1:{s:4:"rest";a:1:{s:7:"baseurl";a:4:{i:0;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.0";}s:8:"_content";s:25:"http://pear.php.net/rest/";}i:1;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.1";}s:8:"_content";s:25:"http://pear.php.net/rest/";}i:2;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.2";}s:8:"_content";s:25:"http://pear.php.net/rest/";}i:3;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.3";}s:8:"_content";s:25:"http://pear.php.net/rest/";}}}}s:6:"mirror";a:2:{i:0;a:2:{s:7:"attribs";a:1:{s:4:"host";s:15:"us.pear.php.net";}s:4:"rest";a:1:{s:7:"baseurl";a:4:{i:0;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.0";}s:8:"_content";s:28:"http://us.pear.php.net/rest/";}i:1;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.1";}s:8:"_content";s:28:"http://us.pear.php.net/rest/";}i:2;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.2";}s:8:"_content";s:28:"http://us.pear.php.net/rest/";}i:3;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.3";}s:8:"_content";s:28:"http://us.pear.php.net/rest/";}}}}i:1;a:2:{s:7:"attribs";a:3:{s:4:"host";s:15:"de.pear.php.net";s:3:"ssl";s:3:"yes";s:4:"port";s:4:"3452";}s:4:"rest";a:1:{s:7:"baseurl";a:4:{i:0;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.0";}s:8:"_content";s:34:"https://de.pear.php.net:3452/rest/";}i:1;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.1";}s:8:"_content";s:34:"https://de.pear.php.net:3452/rest/";}i:2;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.2";}s:8:"_content";s:34:"https://de.pear.php.net:3452/rest/";}i:3;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.3";}s:8:"_content";s:34:"https://de.pear.php.net:3452/rest/";}}}}}}s:13:"_lastmodified";a:2:{s:4:"ETag";s:20:""2fe96-59a-31a3fc80"";s:13:"Last-Modified";s:29:"Tue, 02 Jun 2009 05:55:46 GMT";}}
pecl.php.net.reg
a:6:{s:4:"name";s:12:"pecl.php.net";s:14:"suggestedalias";s:4:"pecl";s:7:"summary";s:31:"PHP Extension Community Library";s:7:"servers";a:1:{s:7:"primary";a:2:{s:6:"xmlrpc";a:1:{s:8:"function";a:10:{i:0;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:9:"logintest";}i:1;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:26:"package.listLatestReleases";}i:2;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:15:"package.listAll";}i:3;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:12:"package.info";}i:4;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:22:"package.getDownloadURL";}i:5;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.1";}s:8:"_content";s:22:"package.getDownloadURL";}i:6;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:25:"package.getDepDownloadURL";}i:7;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.1";}s:8:"_content";s:25:"package.getDepDownloadURL";}i:8;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:14:"package.search";}i:9;a:2:{s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}s:8:"_content";s:15:"channel.listAll";}}}s:4:"rest";a:1:{s:7:"baseurl";a:2:{i:0;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.0";}s:8:"_content";s:25:"http://pecl.php.net/rest/";}i:1;a:2:{s:7:"attribs";a:1:{s:4:"type";s:7:"REST1.1";}s:8:"_content";s:25:"http://pecl.php.net/rest/";}}}}}s:15:"validatepackage";a:2:{s:8:"_content";s:19:"PEAR_Validator_PECL";s:7:"attribs";a:1:{s:7:"version";s:3:"1.0";}}s:13:"_lastmodified";s:31:"Thu, 03 Jan 2008 11:48:06 -0700";}
上記を行った上で、以下を実行。
% sudo pear channel-update pear.php.net % sudo pear channel-update pecl.php.net
これらの作業により、無事 pear でパッケージをインストールすることができるようになる、はずである。
% sudo pear install net_growl-beta downloading Net_Growl-0.7.0.tgz ... Starting to download Net_Growl-0.7.0.tgz (5,176 bytes) .....done: 5,176 bytes install ok: channel://pear.php.net/Net_Growl-0.7.0
MethodInfo.GetGenericArguments() と System.RuntimeType と配列の covariance
概要
配列の covariance のおかげでデバッグに手間が掛かった、という話。
キーワードについて
- MethodInfo.GetGenericArguments メソッド
- これは、メソッドの型引数を表す Type[ ] を返す。
- System.RuntimeType は、System.Type の .net 上における実装。
- System.Type は public abstract class
- System.RuntimeType は internal class
- この RuntimeType は、mono 環境に存在しない。
- 配列の covariance とは、例えば A is B ならば A[ ] を B[ ] に暗黙に変換できるということ
ひどい目
SUtils(https://www.assembla.com/wiki/show/SUtils)の持っているリモートオブジェクト機構(SUtils.Remoting)にて、Generic なメソッドをサポートしていたのだが、これがなぜか mono 上のサーバーと .net 上のクライアントでのみ正常に動作しないというバグが出たのが発端。
このバグでは、mono 環境上のサーバープロセスにて、デシリアライザにて System.RuntimeType が発見できなかった旨の例外が発生していた。
しかしここで不思議なのは、SUtils.Remoting では、RuntimeType を通信に使っている型の定義や、そこから使われている型には含まれないように意図的に排除していたのである。にも関わらず RuntimeType の型情報がシリアライズされ、mono 環境にて検出されてしまったのである。
じつはこれは、MethodInfo.GetGenericArguments() が、Type[ ] ではなく RuntimeType[ ] を返していたのが原因であった。
イメージ図
class HogeMessage{ // Generic なメソッドの型引数を覚えておく // Type や Type[ ] は安心してシリアライズ出来ると分かっている public Type[ ] TypeArguments; } HogeMessage msg = ...; // GetGenericArguments() の戻り型は Type[ ]。 // でも、実際には RuntimeType[ ] インスタンスが戻る。 msg.TypeArguments = method.GetGenericArguments();
すなわち、GetGenericArguments() が返す Type[ ] (上の例で HogeMessage.TypeArguments)をそのままシリアライズした所、それは実際には RuntimeType[ ] であった故に、mono 環境でのデシリアライズで RuntimeType 何それ美味しいの、な状態になってコケてしまったのである。
問題の一般化
今回の事例は、.net と mono 環境の間でシリアライズされたデータを相互にやりとりしていて、かつ片方にしかない型がある、という一般的にはあまりないであろうシチュエーションであった。
しかしながらも、例えばメソッドなどが、シグニチャ上の型とは違う型の配列を返しうるということは、例えば以下のような問題も起こりうることを意味している。
class A{} class B : A{} A[ ] array = (A[ ] を返すメソッドの戻り値); array[0] = new A(); // ArrayTypeMissmatchException
このように、配列の covariance が通る C#/CLI など*1において、GetGenericArguments() のように、シグニチャ上の型(Type[ ])とは違う型の配列(RuntimeType[ ])を戻す実装をすると、使う側にとって予期せぬバグを招くため、危険である。
なので、配列型を返すメンバなどを宣言した場合は、返すインスタンスはその宣言と同じ型になっているべきであろう。
また、GetGenericArguments() のように、正体不明な配列インスタンスを返すメソッドを呼ぶ場合、戻り値の配列はいったん列挙して、必要なら別のコンテナに移すようにした方が、より安全であろう。
(というか、個人的には配列の covariance なんざ使うべきではないとも思っている。そうであるが故に、今回の GetGenericArguments の件は想定外だったのだが・・・。)
Ubuntu 8.04 (hardy heron) に mono 2.4 をインストール
我が家のメインサーバーである Ubuntu 8.04 (近々 OpenSUSE にリプレース予定) に mono 2.4 をインストールしてみたのでメモ。
Ubuntu 8.04 の公式パッケージなどでは mono 1.x になってしまうので、ソースからビルドした。
環境とインストール後の状態
% uname -a Linux NEC-1 2.6.24-16-generic #1 SMP Thu Apr 10 12:47:45 UTC 2008 x86_64 GNU/Linux % lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 8.04.2 Release: 8.04 Codename: hardy
% mono -V Mono JIT compiler version 2.4 (tarball 2009年 5月 5日 火曜日 05:18:44 JST) Copyright (C) 2002-2008 Novell, Inc and Contributors. www.mono-project.com TLS: __thread GC: Included Boehm (with typed GC) SIGSEGV: altstack Notifications: epoll Architecture: amd64 Disabled: none
% gmcs --version Mono C# compiler version 2.4.0.0
やったこと
ほとんど、こちらのサイトを参考にひたすら configure, make, make install しただけである: http://smdn.invisiblefulmoon.net/programming/mono/install_2.4/
上のサイトは Ubuntu 9.04 対象だが、Ubuntu 8.04 でも同様の手順でほぼ問題なくインストール完了した。
問題など
事前にインストール済みの mono を apt-get remove した
手でインストールした mono と共存しても大丈夫かもしれないが、共存させる意味もあまり感じられないので、apt-get install してあった mono は消した。
mono-tools の make で "An exception was thrown by the type initializer for System.Drawing.GDIPlus" となってコケる
以前書いたこのエントリと同じ話である: OS X に macports でインストールした mono で Graphics や Forms を使うための設定 - やこ〜ん SuperNova2
対処法としては、~/.mono/config に
<configuration> <dllmap dll="gdiplus.dll" target="/usr/local/lib/libgdiplus.so"/> </configuration>
と書いておけばよい(当然ながら事前に libgdiplus のインストールが必要である)。
こうすることで、gdiplus.dll を発見できないためにコケているところを、gdiplus.dll の代わりに libgdiplus.so をロードさせることで解決できるようになる。
gnome-desktop-sharp が gtksourceview2-sharp.dll: no と主張する
gnome-desktop-sharp の configure にて、
% gacutil -l | grep source gtksourceview-sharp, Version=1.0.0.2, Culture=neutral, PublicKeyToken=35e10195dab3c99f gtksourceview2-sharp, Version=2.0.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f
という風に、gtksourceview2 がインストールされているはずであるにも関わらず、
Optional assemblies included in the build: * gtkhtml-sharp.dll: yes * gtksourceview2-sharp.dll: no * nautilusburn-sharp.dll: yes * rsvg-sharp.dll: yes * vte-sharp.dll: yes * wnck-sharp.dll: yes
となってしまう問題がある。
gacutil -l では出てくるのに見つからないあたり、何か問題があるのだろうが、今のところ gtk / mono なアプリを触る予定が無い上、おなかが減ったのでとりあえず放置してある。
おそらく、gtksourceview を使えないことで MonoDevelop などで困るだけだと思うので、後日気が向いたらどうにかする予定。
System.Type.GUID プロパティと GuidAttribute
ref: Type.GUID プロパティ (System)
ref: GuidAttribute クラス (System.Runtime.InteropServices)
まとめ
- GuidAttribute を型に付けることで、Type.GUID の値を指定できる
- GuidAttribute を付けておけば、コンパイラやランタイムに依存せずに Type.Guid を取得できる
- GuidAttribute が無い場合の Type.GUID の値は、ランタイムに依存
- .net framework なら、型の完全限定名を元に GUID を振ってくれる
- mono の場合、GuidAttribute が存在しない限り、Type.GUID は Null Guid*1 になる
- Type.GUID は、現実的には COM Interop 用
そもそも Type.GUID は何のためにあるのか
MSDN の Type.GUID の解説には「GUID は、GuidAttribute 属性を使用して型に関連付けられています。」とあるので GuidAttribute についての記述を見てみると、タイプライブラリ インポーターが使う旨が読み取れる。
このことから、Type.GUID は COM Interop のためにあると考えられる。
任意の型について、GUID はどのようにして決定されるのか
Generic 型について
Generic な型については、Type.GUID は型引数が与えられているかどうかに関わらず同じ値を返す。
つまり、
typeof(List<>).GUID typeof(List<string>).GUID typeof(List<int>).GUID
は全て同じ Guid を返す。
よって Generic な型もそうでない型も Type.Guid に関する振る舞いは同じなので、以下では区別せずに扱う。
GuidAttribute がある場合
Type.Guid{ get; } は、GuidAttribute で指定した GUID をそのまま返す。
これについては、csc.exe、gmcs のどちらでコンパイルしても、.net framework、mono のどちらで実行しても正しく動作する。
GuidAttribute がない場合
GuidAttribute がない場合には、Type.GUID の値はランタイムで決定される。つまり、アセンブリを作ったコンパイラではなく、実行している環境に依存した値になる。
具体的には、
- .net framework の場合: 型の完全限定名*2を元にした値
- mono の場合: Null Guid
が得られる。
なお、前者については、GuidAttribute の無い適当な型を作って、型の完全限定名を変えながら実行してみると分かるが、たとえクラスの中身が別物だろうと完全限定名が一致すれば同じ Type.GUID になる。
また、後者については、Re: (Mono-dev) Type.GUID patch の議論にあるとおり、
といった理由があるようだ。
Type.GUID は COM 相互運用のための値
個人的に Type.GUID が気になった理由は、自作シリアライザなどで、型の完全限定名を毎回付加するのがサイズ的に大きくて悲しいことへの対処に使おうと考えたからであった。
のだが、この状況を見るに、Type.GUID は、「Type に対応する GUID 値」でなく、(おそらく MS の人が意図しているであろう通り、) COM 相互運用のための値と割り切ったほうが無難なようだ。
xterm-color の TermInfo がない @ Solaris
ref: http://www.linux.or.jp/JF/JFdocs/Text-Terminal-HOWTO-15.html
最近新しい環境に ssh でログインすることがあったのだが、その際に
- vi, emacs, less などなどがまともに立ち上がらない
- シェルによっては Backspace などが効かない
ということがあってハマったのでメモ。
結局、これは端末の種類に対応する terminfo がなかったために起きていたようだ。
Terminfo とは
先頭にある ref 先に詳しいが、要するに端末の種類(vt100, xterm, ...)に応じた制御文字のデータベースである。
less, vi, emacs, (bashでないある種の)シェルなどは、ここから得られる情報を使って制御文字を吐くことで端末の出力を制御する*1。
ので、自分の使っている端末に応じた terminfo が当該環境に無いと、悲しいことが起こるのである。
悲しいことの例
今回問題になった環境(Solaris)では、less や emacs が以下のようなエラーメッセージを出してくれたおかげで解決できた次第。最初は zsh が何もエラーを出さずに、かつ挙動がおかしかったりしたのでハマった・・・。
less のエラーメッセージ:
WARNING: terminal is not fully functional
emacs のエラーメッセージ:
emacs: Terminal type xterm-color is not defined. If that is not the actual type of terminal you have, use the Bourne shell command `TERM=... export TERM' (C-shell: `setenv TERM ...') to specify the correct type. It may be necessary to do `unset TERMINFO' (C-shell: `unsetenv TERMINFO') as well.
対処
使っている端末の種類は $TERM 環境変数を読めば出てくる。筆者の場合、
$ echo $TERM xterm-color
だった。
で、それに対応する Terminfo が存在するかを確認するには、locate コマンドを使ってファイルを探すのが早い、と先述の ref 先には書いてあった。のだが、いかんせん件の環境では locate が core を吐いて落ちる。どうやら、調べてみたところ、Solaris の terminfo は /usr/share/lib/terminfo に置いてあるそうである。
TERMINFO – terminfo データベースに追加された、デフォルトでない端末のパス名を指定します。terminfo データベース内のデフォルト端末については、この変数を設定する必要はありません。terminfo データベースの詳細は、『Solaris のシステム管理 (上級編)』を参照してください。
TERM – 現在使っている端末を指定します。エディタを実行するときは、TERM 変数で定義された名前と同じ名前のファイルが検索されます。その場合、まず最初に TERMINFO 変数で指定されるパスが検索され (TERMINFO が定義されている場合)、次にデフォルトディレクトリの /usr/share/lib/terminfo が検索されて、端末の特性が決定されます。TERM 変数で定義されたファイルが見つからない場合は、その端末はダム端末と認識されます。
http://docs.sun.com/app/docs/doc/816-3946/6ma6m5bp5?l=Ja&a=view
また、上にもあるとおり、デフォルトの terminfo の置き場所(/usr/share/lib/terminfo)の前に、$TERMINFO で設定したパスを探索してくれる。
terminfo の置き場は、先頭一文字のディレクトリをほって、その下にコンパイル済みの*2 terminfo ファイルを置いてやると良いようだ。
件の環境での例:
$ ls /usr/share/lib/terminfo 1 3 5 7 9 B H P a c e g i k m o q s u w y 2 4 6 8 A G M S b d f h j l n p r t v x z $ ls /usr/share/lib/terminfo/x x1700 x1750 xitex xpcterm xtalk xtermc xterms x1720 x820 xl83 xpcterms xterm xtermm
この辺の挙動についても、冒頭の ref 先の説明が分かりやすい*3。
そこで、
$ ls ~/.terminfo x $ ls ~/.terminfo/x xterm-color
という構造になるように terminfo を置いてから、
export TERMINFO=/home/myname/.terminfo
interface 型の Type.GetMembers や GetMethods では継承元は無視される
ref: Type.GetMethods Method (BindingFlags)
具体的にどういう事かは ref 先のユーザーフィードバックのコメントに詳しいが、要するに interface の Type に対して GetMembers などを呼んだ場合、基底 interface のメンバーは列挙されないということである。
したがって、interface の全メンバーなりメソッドなりを列挙するつもりで GetMembers や GetMethods を使うと、派生元になっている interface のメンバーが出てこなくなって悲しいことになる。のでこのようなエントリを書くに至った次第*1。
対処法としては、ref 先にもあるように GetInterfaces() を使って、祖先インターフェースたちのメンバーを再帰的に列挙する他にないだろう。
なお、ref 先では HashSet
そのため、mono 環境でも動く、ランタイムの実装に依存しないコードにするためには、再帰的列挙の過程で過去に列挙済みの interface の型を覚えておくようにし、そもそも同一 interface は必ず一回だけ列挙するようにすれば良いだろう。
具体的には、以下のようなコードが上記を踏まえた workaround になるだろう。
/// <summary> /// /// </summary> [ThreadStatic] private static Dictionary<KeyValuePair<Type, BindingFlags>, MethodInfo[]> allMethods; /// <summary> /// Returns all <see cref="MethodInfo"/>s of given <paramref name="type"/>. /// Even if given type is interface, this method returns all methods (unlike type.GetMethods). /// </summary> /// <param name="type"></param> /// <param name="flags"></param> /// <returns></returns> public static IEnumerable<MethodInfo> GetAllMethods(this Type type, BindingFlags flags) { MethodInfo[] methods; var key = new KeyValuePair<Type, BindingFlags>(type, flags); if (allMethods == null) allMethods = new Dictionary<KeyValuePair<Type, BindingFlags>, MethodInfo[]>(); if (!allMethods.TryGetValue(key, out methods)) { allMethods[key] = methods = (type.IsInterface) ? type.GetAllMethodsOfInterface(flags).ToArray() : type.GetMethods(flags); } return methods; } /// <summary> /// /// </summary> /// <param name="interfaceType"></param> /// <param name="flags"></param> /// <returns></returns> private static IEnumerable<MethodInfo> GetAllMethodsOfInterface(this Type interfaceType, BindingFlags flags) { if (!interfaceType.IsInterface) throw new ArgumentException("Given type isn't interface: " + interfaceType.Name); return interfaceType.GetAllMethodsOfInterface(flags, new HashSet<Type>()); } /// <summary> /// /// </summary> /// <param name="interfaceType"></param> /// <param name="flags"></param> /// <param name="visitedInterfaces"></param> /// <returns></returns> private static IEnumerable<MethodInfo> GetAllMethodsOfInterface(this Type interfaceType, BindingFlags flags, HashSet<Type> visitedInterfaces) { if (!interfaceType.IsInterface) throw new ArgumentException("Given type isn't interface: " + interfaceType.Name); if (!visitedInterfaces.Add(interfaceType)) return Enumerable.Empty<MethodInfo>(); return interfaceType.GetMethods(flags).Concat( interfaceType.GetInterfaces().SelectMany((p) => p.GetAllMethodsOfInterface(flags, visitedInterfaces)) ); }