yokohama.pm tech talk #5
OpenSocial なモバイルアプリを書く場合、アプリ単体でテストできるようにしてくれる Moxy の OpenSocial プラグインの話と、外部 API 呼び出しを専用に行う非同期なプロクシサーバーの話をしました。
資料はこちら:
前者に関しては、個人的には OpenSocial モバイルアプリ開発には必須なツールなので良い感じにしていきたいところ。 &してくれるひと募集。
後者のプロクシサーバーは「あんまり頭よくない」だけれども、現在のウェブアプリケーションの構成で、API呼び出しの待ち時間をうまく使おうとするときにはこうなるのかなぁと思った。同じようなことをしているところもあるらしい!
個人的にはそれ〜でできるよ!ってのがないかなーと思っていたのだけど、ないっぽいのかなー。
nginx でおしいところまではできるので、モジュール書いたら出来るのか調べてみようと思う。
local::lib を切り替える
レガシーなアプリをメンテするのにレガシーな Perl モジュールが必要になることがあり、そのために古いアプリ用には専用の local::lib ディレクトリを切っているわけですが、普段から日常的に local::lib を使用しているため shell が上がったタイミングではデフォルトの local::lib 環境変数がセットされていて切り替えが非常に面倒だった。
これまではそれほど頻繁に使用しなかったので放置していたのだが、ここのところ頻繁に必要になるためいい加減うざくなってきて簡単に local::lib を切り替えられるよう設定をしてみた。
zsh に次のような関数をつくり、それで local::lib を切り替えるようにする。
function locallib () {
INSTALL_BASE=$1
if [ -d $INSTALL_BASE ]; then
eval $(~/bin/use-locallib $INSTALL_BASE)
fi
}
これは
$ locallib ~/perl5
などのように INSTALL_BASE を指定して使う。指定されたパスが存在したら use-locallib というコマンドを使用して環境変数を切り替えるという内容。
この use-locallib コマンドは以下のようなソースになっていて、古い local::lib の環境変数をクリアするとともに、新しい local::lib の環境変数を print するというものになっている。
#!/usr/bin/env perl
use strict;
use warnings;
use Pod::Usage;
use Config;
use File::Spec;
my $install_base = $ARGV[0]
or pod2usage(-1);
$install_base = File::Spec->rel2abs($install_base);
my $path = $ENV{PATH};
my $perl5lib = $ENV{PERL5LIB};
push @INC, File::Spec->catdir($install_base, 'lib', 'perl5');
require local::lib;
my %env = local::lib->build_environment_vars_for($install_base, 1);
# remove $PERL5LIB set by old local::lib if it exists.
if (my $old_base = $ENV{PERL_MM_OPT}) {
my %mmopt;
for my $opt (split /:+/, $old_base) {
my ($k, $v) = split /=/, $opt;
$mmopt{$k} = $v;
}
if (my $old_installbase = $mmopt{INSTALL_BASE}) {
if ($old_installbase eq $install_base) {
# do nothing if install_base is equal to old one
exit;
}
my @old_perl5lib = (
File::Spec->catdir($old_installbase, 'lib', 'perl5'),
File::Spec->catdir($old_installbase, 'lib', 'perl5', $Config{archname}),
);
$env{PERL5LIB} = do {
my @env;
ENV: for my $e (grep { $_ } split $Config{path_sep}, $env{PERL5LIB}) {
for my $old (@old_perl5lib) {
next ENV if $old eq $e;
}
push @env, $e;
}
join $Config{path_sep}, @env;
};
my $old_path = File::Spec->catdir($old_installbase, 'bin');
$env{PATH} = do {
my @p;
for my $p (grep {$_} split $Config{path_sep}, $env{PATH}) {
next if $p eq $old_path;
push @p, $p;
}
join $Config{path_sep}, @p;
};
}
}
while (my ($k, $v) = each %env) {
print qq[export $k="$v"\n];
}
=head1 NAME
use-locallib - set/switch local::lib environment
=head1 SYNOPSIS
use-locallib (MODULE INSTALL BASE)
これで local::lib を簡単に切り替えられるようになるけれど、複数の local::lib 環境を同時に使用しているとどのシェルがどの local::lib を使用しているかわからなくなってしまう。
そのためシェルのプロンプトに INSTALL_BASE を表示されるようにしてしのいだ。
これは単純に以下のような PERL_MM_OPT の INSTALL_BASE をプリントするスクリプトを PROMPT 設定から読んでいるだけである。
#!/usr/bin/env perl
use strict;
use warnings;
my %mm_opt;
for my $opt (split /:+/, $ENV{PERL_MM_OPT} || '') {
my ($k, $v) = split /=/, $opt;
$mm_opt{$k} = $v;
}
my $install_base = $mm_opt{INSTALL_BASE};
if ($ENV{HOME}) {
$install_base =~ s/^$ENV{HOME}/~/;
}
print $install_base || 'none';
ライブラリをユニバーサルバイナリでインストールする
以下のサイトが詳しい
http://macwiki.sourceforge.jp/wiki/index.php/UniversalBinary
Imager に必要な libjpeg などをユニバーサルバイナリにしてみた時のメモ
-M などがついていて -arch が複数指定できない場合以外は
CFLAGS='-arch x86_64 -arch i386 -isysroot /Developer/SDKs/MacOSX10.6.sdk' CXXFLAGS=$CFLAGS
とかすればいいということだが、libjpeg は -M られていて無理だったので、amd64 と i386 という二つのディレクトリにソースコードを展開、それぞれ以下のオプションで make まで終わらす:
$ cd amd64
$ CFLAGS='-arch x86_64 -isysroot /Developer/SDKs/MacOSX10.6.sdk' CXXFLAGS=$CFLAGS ./configure ...
$ make
$ cd ../i386
$ CFLAGS='-arch i386 -isysroot /Developer/SDKs/MacOSX10.6.sdk' CXXFLAGS=$CFLAGS ./configure ...
$ make
make されてできたバイナリを lipo コマンドでユニバーサルバイナリ化する。 上記サイトを参考にして、
#!/bin/sh
filelist=$(find ./amd64 -type f |grep -v \\.o$ | xargs file | sed -e 's,^\./amd64/,,g' | \
grep -E \(Mach-O\)\|\(ar\ archive\) |sed -e 's,:.*,,g' -e '/\for\ architecture/d')
for i in $filelist
do
echo $i
/usr/bin/lipo -create amd64/$i i386/$i -output `basename $i`
mv -f `basename $i` amd64/$i
done
このようなスクリプトを書いて、amd64 などのディレクトリの一つ上の階層で実行すると、amd64 ディレクトリのバイナリがユニバーサルバイナリ化されるという仕組みを作ってみた。
その後、amd64 のほうで make install してやれば OK。
Snow Leopard の Perl とアーキテクチャ
Snow Leopard には二つの Perl がインストールされている
- /usr/bin/perl5.10.0
- /usr/bin/perl5.8.9
デフォルトの /usr/bin/perl は 5.10.0 のほう。それぞれユニバーサルバイナリになっていて、
$ file /usr/bin/perl5.10.0
/usr/bin/perl5.10.0: Mach-O universal binary with 3 architectures
/usr/bin/perl5.10.0 (for architecture x86_64): Mach-O 64-bit executable x86_64
/usr/bin/perl5.10.0 (for architecture i386): Mach-O executable i386
/usr/bin/perl5.10.0 (for architecture ppc7400): Mach-O executable ppc
$ file /usr/bin/perl5.8.9
/usr/bin/perl5.8.9: Mach-O universal binary with 2 architectures
/usr/bin/perl5.8.9 (for architecture i386): Mach-O executable i386
/usr/bin/perl5.8.9 (for architecture ppc7400): Mach-O executable ppc
という感じで、5.10 は 64bit 版があるが、5.8 にはない。
で、普通に perl を実行すると perl5.10.0 は x86_64 で実行され、perl5.8.9 は i386 で実行される。
このアーキテクチャの差が結構くせ者で Snow Leopard 上で普通にライブラリなどを make すると x86_64 だけでビルドされてしまうため、そのようにして作ったライブラリはそのままでは perl5.8.9 からは使えないということになる。
デフォルトの 5.10.0 だけ使っている分にはなにも問題はないのだが、残念なことにこの 5.10.0 というのはいろいろな問題があり、開発に使用することはおすすめできない状況。(せめて 5.10.1 にしてくれればいいのに)
したがって無用なトラブルを避けるためにも 5.8.9 の方を使用するか、自前でビルドした perl を使用するのが良い。
またこのように x86_64、i386 両方のアーキテクチャの実行ファイルがあり得る Snow Leopard に対して自分でライブラリをインストールする場合それらもユニバーサルバイナリにしておくと良い。
念のため追記@2010-01-08T15:19:23+09:00: もちろんこれは 64bit 対応の CPU の場合の話。そうでない場合はどちらも i386 で実行されるのでこの問題は起こらない。
CocoaEmacs おかしいところ
anything-config.el も無事なおり、Shiftのバグなおして日本語入力も快適になり、あとはまぁだいたいいい感じに使えるようになった(現状では) CocoaEmacs だけど一点だけおかしいところがある。
僕は結構 Emacs の customize 機能をヘビーユーズしてて(コーディングしてて気に入らない色設定とか出てきたらその場ですぐ customize-face する)これで色設定はほぼ調整してるのだけど、customize するのをすべての Emacs で同じものを使うのは微妙なので、
(setq custom-file "~/.emacs.d/conf/cocoa-emacs-99customize.el")
のようにして、 customize の保存先を Emacs 毎に変えている。
のだけどこれが、init-loader のフェーズで (load custom-file) しても設定が適用されないという現象に悩まされている。
立ち上がったあとに手動で (load custom-file) するとロードされる。。なぞ。
しょうがないので、
(defun reload-custom-file ()
(interactive)
(load custom-file))
こんなの作っておいて起動後手動で実行している。不毛だけどまぁ Emacs たちあげっぱだからいいかなぁ。。
elispエラーった時の追い方
(setq debug-on-error t)
しておいて、eval-bufer。StackTrace 出るのでそれを見る。
Emacs 23 にしたら anything-config.el が Invalid depth in char-table とかいって死ぬようになったけど原因わからなくてこまってたけどこれやったら ~/.emacs.d/anything-c-adaptive-history のロードに失敗してるのがわかった。消したらなおりました。imakado++
CocoaEmacsその後
フルスクリーン化以外にもいじりたくなったのでそれぞれブランチを切った。
- feature/fullscreen - 今まで作業していたフルスクリーン化対応ブランチ
- fix/shift_modifier_with_ime - IME経由で入力するときShiftなんちゃらがIMEを素通りするのを直すブランチ
という感じになっています。 master はこれらの統合ブランチになってますので使ってる人いたら注意です。
使いながらちょいちょいいじっていて、昨日からの更新としては
- フルスクリーン時に裏にノーマルなウィンドウが残ってしまっていたのを修正
- 日本語入力時に Shift なんちゃらが IME をとおらずそのまま Emacs に渡されてしまっているのを修正
という感じになっております。
CocoaEmacsのフォント設定
現状こんな感じ。以前書いたCarbonEmacsの設定と大して変わってない。unicode コードセットで表示される文字が増えていたのでそれに対応した程度。
(create-fontset-from-fontset-spec
(concat
"-*-fixed-medium-r-normal--12-*-*-*-*-*-fontset-tobi"
",ascii:-apple-codingfonttobi-medium-r-normal--16-120-72-72-m-120-*-*"
",japanese-jisx0208:-apple-osaka-*"
",katakana-jisx0201:-apple-osaka-*"
",unicode:-apple-osaka-*"
",chinese-big5-1:-apple-apple ligothic medium-*"
))
(set-default-font "fontset-tobi")
ascii 用につかってる CodingFontTobi は proggyfonts.com から。
もちろんビットマップ表示にするため
$ defaults write org.gnu.Emacs AppleAntiAliasingThreshold 16
としている。
(setq mac-allow-anti-aliasing nil)
は Snow Leopard では効かないみたいだ。
フルスクリーン Cocoa Emacs
emacs23 が正式にリリースされ、ちまたではウィンドウシステムが Cocoa になったぞ、とか 64bit 対応だぞ、とか multitty だぞ、と盛り上がっていますが、 個人的にほとんど興味のないまま今日まできました。というのも CocoaEmacs はフルスクリーンにできないという僕にとっては致命的な問題があったからです。
というのも最近は Emacs をフルスクリーンにし縦二分割で使ういわゆる imakado スタイルを実践しているため、それができない CocoaEmacs は眼中になかったのでした。
そんなわけでずっと CarbonEmacs を使ってきたのですが、 Snow Leopard に移行したのを機に CocoaEmacs に移行し、さらについでにフルスクリーン化できるようにしてみています。 github でやってます。
現状でも若干不具合がありますが、使える段階にはなっていると思います。
ビルドは
$ git clone git://github.com/typester/emacs.git
$ cd emacs
$ ./configure --with-ns
$ make bootstrap
$ make install
こんな感じにすると、nextstep ディレクトリ以下に Emacs.app ができます。フルスクリーンにするには
M-x ns-toggle-fullscreen
でいけます。キー割り当てておくと便利です。
対応環境は定かではありませんが、OS X 10.4 と 10.6 で動作確認しております。
また 10.6 の場合のみ、フルスクリーン時に隠れているメニューバーや Dock 領域にマウスカーソルを移動させると自動的に表示されるようになっています。 CarbonEmacs と違い Dock も自動で表示されるので個人的に非常に便利です。10.6 からの新 API のため、10.5 以前ではフルスクリーン時にメニューバー、Dock は強制オフになります。
既知の不具合として外部ウィンドウでフルスクリーン化して戻そうとすると落ちるというのが報告されており調査中です。 それ以外は便利に使用できています。この文章もフルスクリーン CocoaEmacs で書いています ;)
またこのハックをしたことで elisp から C や Objective-C のコードを呼ぶ方法がわかったのでいろいろ夢がひろがりんぐというところです。
A simple chat server in AnyEvent
Node.js でつくってるやつ をみて同じくらいで書けそうだなと思ったので試しに AnyEvent で書き直してみた。
#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent::Socket;
use AnyEvent::Handle;
my @clients;
tcp_server undef, 7000, sub {
my ($fh) = @_ or die $!;
my $h = AnyEvent::Handle->new( fh => $fh );
my $leave = sub {
my $client = delete $clients[ fileno($fh) ];
for my $c (grep { defined } @clients) {
$c->{handle}->push_write("$client->{name} has left.\n");
}
};
$h->on_read(sub {
shift->push_read( line => sub {
my ($h, $line) = @_;
my $client = $clients[ fileno($fh) ];
unless (defined $client->{name}) {
if ($line =~ /(\S+)/) {
$client->{name} = $1;
$h->push_write("===========\n");
for my $client (grep { defined } @clients) {
next if $client->{handle} eq $h;
$client->{handle}
->push_write( "$client->{name} has joined.\n" );
}
}
return;
}
my ($command) = $line =~ m!^/(.+)!;
if ($command) {
if ($command eq 'users') {
$h->push_write("- $_->{name}\n") for grep { defined } @clients;
}
elsif ($command eq 'quit') {
$leave->();
}
return;
}
for my $c (grep { defined } @clients) {
next if $c->{handle} eq $h;
$c->{handle}->push_write( "$client->{name}: $line\n" );
}
});
});
$h->on_error(sub {
my ($h, $fatal, $msg) = @_;
$leave->();
$h->destroy;
});
$clients[ fileno($fh) ] = {
name => undef,
handle => $h,
};
$h->push_write("Welcome, enter your username:\n");
};
AE::cv->wait;
行数的にはほぼ同じくらい。
追記@2009-12-08T10:36:37+09:00: left なメッセージがおかしかったので直した。
