RubyGems 2.2 から拡張ライブラリのインストール先が変わったっぽい
ruby 2.1.0-preview2 に同梱されている RubyGems は 2.2 であり、このバージョンから拡張ライブラリのインストール先が変わったっぽい。まだ preview なので正式版でもこのままなのかはわからないけど…
今までは lib/ruby/gems/2.1.0/gems/foo-0.1.0/lib
みたいなディレクトリが基点になっていて普通の Ruby のコードと同じ場所に置かれていたけど、RubyGems 2.2 から新たに lib/ruby/gems/2.1.0/extensions
というディレクトリができ、lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/foo-0.1.0
のようなディレクトリが基点になる。この変更に関係する pull-request はたぶんこれ https://github.com/rubygems/rubygems/pull/596 。
extensions 以下のディレクトリも自動的に $LOAD_PATH
に追加されるので普通は影響がないはずだけど、lib 以下に拡張ライブラリも存在すると仮定した処理を行っている場合は動かなくなってしまう。 実際に msgpack-ruby がこれに該当していて、先週直されてた https://github.com/msgpack/msgpack-ruby/pull/22 。
ScanSnap iX500 を VirtualBox 内の Windows 8 から使う
ScanSnap iX500 と Windows 8 を買った。 サーバ的に使っている Linux マシンの VirtualBox 内に Windows 8 をインストールして、そこから iX500 を使っている。
Linux から全く使えないわけではなくて、sane を使うとスキャンはできる。 現時点で最新のリリース版である 1.0.23 は iX500 の発売より前のリリースなので、当然対応しておらず、HEAD をビルドする必要がある。
ただし、本当に画像としてスキャンができるだけで、サイズを自動的に調節したりカラーと白黒の判別を自動的にしたり OCR をかけて pdf にしたりといったことができないため、俺は勧めない。
なので素直に Windows 8 から使うようにした。ただし VirtualBox 内の。 準備として、まず Oracle Extension Pack をインストールする。これはリモートから VirtualBox のゲストマシンに VRDP で接続するのに必要なため。 また、ホストマシンに差した USB をゲストマシンから使う際にユーザが vboxusers グループに属している必要があるので、これも行っておく。
% yaourt -S virtualbox virtualbox-ext-oracle
# gpasswd -a eagletmt vboxusers
次に仮想マシンを用意する。VirtualBox では VBoxManage
でコマンドラインから操作できる。
% VBoxManage createvm --name Win8 --ostype Windows8_64 --register
% VBoxManage modifyvm Win8 --memory 2048 --boot1 dvd --nic1 nat --acpi on --vrde on --usb on --usbehci on --cpus 2 --vram 64
% VBoxManage createhd --filename Win8.vdi --size 40960
% VBoxManage storagectl Win8 --name Win8sata1 --add sata --bootable on
% VBoxManage storageattach Win8 --storagectl Win8sata1 --port 1 --type hdd --medium ~/VirtualBox\ VMs/Win8/Win8.vdi
% VBoxManage storageattach Win8 --storagectl Win8sata1 --port 2 --type dvddrive --medium /tmp/win8.iso
ここでやっていることは、
- Win8 という名前の仮想マシンを作成する
- Win8 という仮想マシンの設定を行う
- メモリは 2GB
- ブートの順序の最初は dvd
- ネットワークアダプタの割り当ては NAT
- ACPI をサポート
- VRDP をサポート (リモートから
rdesktop-vrdp
でゲストマシンに接続できる) - USB コントローラを有効化
- USB 2.0 (EHCI) コントローラを有効化
- CPU は2つ (最初1コアでやったらスキャンがだいぶ遅れることがあったので)
- VRAM は 64MB (デフォルトでは足りなかったので適当に増やした)
- Win8.vdi という 40GB のディスクイメージを作成
- Win8sata1 という仮想 SATA コントローラを作成
というもの。EHCI コントローラを有効にし忘れると、iX500 を接続したのになぜか認識されないということになる。
そして
% VBoxHeadless --startvm Win8
で起動できる。 クライアントからは
% rdesktop-vrdp $REMOTE_IP
で接続できるので、Windows 8 のインストール作業を行う。VirtualBox Guest Additions もインストールしておく。
USB デバイスをゲストマシンに認識させるには、さらに USB デバイスフィルタを設定する必要がある。 まず VirtualBox がちゃんと USB デバイスを認識できてるかどうかを確認する。
% VBoxManage list usbhost
Host USB Devices:
UUID: 0d4dadeb-a48e-4be1-a412-b79b28f1c917
VendorId: 0x04c5 (04C5)
ProductId: 0x132b (132B)
Revision: 1.0 (0100)
Port: 5
USB version/speed: 2/2
Manufacturer: Fujitsu
Product: ScanSnap iX500
Address: sysfs:/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1/3-1.6//device:/dev/vboxusb/003/006
Current State: Available
これが表示されないときは、ユーザが vboxusers グループに所属していない可能性がある。 iX500 だけをパスさせるようにするため、vendor id と product id を指定してフィルタを作る。
% VBoxManage usbfilter add 0 --target Win8 --name ScanSnap --vendorid 04C5 --productid 132B
ちなみにこの vendor id と product id は lsusb
でも調べられる。
そうしてから Win8 を立ち上げる。すると、usbhost の Current State が Captured に変化するはずである。
% VBoxHeadless --startvm Win8 &
[1] 16562
Oracle VM VirtualBox Headless Interface 4.2.16_OSE
(C) 2008-2013 Oracle Corporation
All rights reserved.
VRDE server is listening on port 3389.
% VBoxManage list usbhost
Host USB Devices:
UUID: 33fd08be-3bab-4280-8545-290ffffe4a8a
VendorId: 0x04c5 (04C5)
ProductId: 0x132b (132B)
Revision: 1.0 (0100)
Port: 5
USB version/speed: 2/2
Manufacturer: Fujitsu
Product: ScanSnap iX500
Address: sysfs:/sys/devices/pci0000:00/0000:00:1a.0/usb3/3-1/3-1.6//device:/dev/vboxusb/003/006
Current State: Captured
この状態ならゲストマシンから iX500 を認識できているはずなので、iX500 のインストールディスクを VBoxManage storageattach
で繋げてインストール作業をすれば使えるようになる。
スキャンしたデータは共有したいので、VirtualBox の共有フォルダ機能を使った。
% VBoxManage sharedfolder add Win8 --name scansnap --hostpath ~/scansnap
ゲストマシンでは \\VBOXSVR\scansnap にスキャンデータを置くようにする。 俺の場合はさらに ~/scansnap をホスト側で NFS で export するようにしている。
こんなかんじで iX500 を使ってますが、それなりに快適です。
参考
- Chapter 7. Remote virtual machines http://www.virtualbox.org/manual/ch07.html
- Chapter 8. VBoxManage http://www.virtualbox.org/manual/ch08.html
neco-ghc で補完時に型情報を表示できるようにした
ghc-mod のほうに補完に使えそうな機能追加があったので久しぶりに neco-ghc を更新した https://github.com/ujihisa/neco-ghc
vimrc で let g:necoghc_enable_detailed_browse = 1
とすると補完時に型情報が表示されるようになる。 ghc-mod 1.11.5 で追加された機能を使っているので、有効化するにはそれ以上のバージョンが必要。
変数だけではなく型クラスや型シノニム等にも表示される。
欠点としては、無効化したときと比べてやや初期化に時間が掛かることが挙げられる。 キャッシュされるので最初さえ我慢できれば従来通り高速に補完候補が表示される。
YARD で拡張ライブラリのドキュメントを書くときに気をつけること
Ruby の拡張ライブラリを書いていていて、YARD でドキュメントを生成しようと思ったらうまく生成されなくてしばらくはまっていた。
YARD も RDoc も、pure Ruby なライブラリだけでなく C で書く拡張ライブラリのドキュメンテーションにも対応している。 そのはずなんだけど、RDoc ではちゃんとメソッドのドキュメントが生成されているのに、YARD だと生成されない、という現象に悩まされた。 いろいろ調べた結果、YARD は簡単に言うと
- Ruby のメソッドの本体として使われていそうな関数定義を見つけたら、関数名からそのコメントや定義へのマッピングを追加 (symbol_handler.rb)
- 関数内で
rb_define_method
しているのを見つけたら、マッピングからコメントや定義を取得して、メソッド名と関連付ける (method_handler.rb)
みたいなアルゴリズムになっていて、つまり rb_define_method
している行よりソースコード的に上でコメント付きのメソッド本体の関数の定義 (!= 宣言) する必要がある。
なので、
#include <ruby.h>
VALUE rb_cFoo;
static VALUE foo(VALUE self, VALUE x);
void Init_foo(void)
{
rb_cFoo = rb_define_class("Foo", rb_cObject);
rb_define_method(rb_cFoo, "foo", RUBY_METHOD_FUNC(foo), 1);
}
/* call-seq: foo(x)
*
* Foo method
*
* @param [Fixnum] x the argument
* @return [Fixnum] the return value
*/
VALUE foo(VALUE self, VALUE x)
{
return x;
}
みたいに書くと YARD のドキュメンテーションに失敗する。
#include <ruby.h>
VALUE rb_cFoo;
/* call-seq: foo(x)
*
* Foo method
*
* @param [Fixnum] x the argument
* @return [Fixnum] the return value
*/
static VALUE foo(VALUE self, VALUE x)
{
return x;
}
void Init_foo(void)
{
rb_cFoo = rb_define_class("Foo", rb_cObject);
rb_define_method(rb_cFoo, "foo", RUBY_METHOD_FUNC(foo), 1);
}
こう書くと成功する。
一方 RDoc では、どっちで書いてもちゃんとメソッドに対するドキュメントが生成される。
NFS をマウントしたままシャットダウンすると異常に時間がかかる問題
NFS をマウントしたままシャットダウンすると、電源が切れるまでに1分以上かかった。 しかし手動で NFS を umount し (これはすぐ終わる)、シャットダウンすると普通にスムーズにシャットダウンする。
ちょうど同じような症状で悩んでいる人が arch-general にいて、そこで解決しているのを見つけた https://mailman.archlinux.org/pipermail/arch-general/2013-January/032794.html 。 自分の環境でもこの方法で解決した。
原因は NFS を umount する前にネットワーク接続が切れていることだった。 これを解決するには、systemd に NFS にはネットワーク接続が必要であることを伝えればいい。 systemd はブート時に、/usr/lib/systemd/system-generators/systemd-fstab-generator で /etc/fstab から mount unit を生成する。 これは systemctl list-units
で見ることができる。 systemctl list-units
すると
mnt-nfs.mount loaded active mounted /mnt/nfs
みたいな行があるので、ここからマウントポイントに対応する mount unit の名前がわかる。 あとは、これがネットワーク接続を必要としていることを設定すればいい。 自分も arch-general で回答している人のように dhcpcd@.service を使っていたので、
# mkdir /etc/systemd/system/mnt-nfs.mount.wants # ln -s /usr/lib/systemd/system/dhcpcd@.service /etc/systemd/system/mnt-nfs.mount.wants/dhcpcd@eth0.service
とした。
Arch のパッケージのリポジトリを作る
Arch には公式のリポジトリとして core, extra, community, testing 等があるが、それ以外に非公式なものもいくつかある。 その中でも特に archlinuxfr は有名だと思う。linux-pae, linux-pf, repo-ck といったパッチをあてたカーネルのパッケージを提供しているリポジトリを利用している人もいるかもしれない。 そういった非公式リポジトリは wiki にまとまっている: Unofficial User Repositories - ArchWiki 。
自分も自分用に独自のリポジトリを作ってみたので、その手順を紹介する。 パッケージを作るための PKGBUILD は既に書いてあるとする。
ビルド環境の準備
まずビルド用のユーザ archbuild を作った。 ビルド用のツールは devtools というパッケージにまとまっているので、pacman -S devtools
でインストールしておく。 archbuild は一部 root になる必要があるので、visudo で /usr/sbin/makechrootpkg を NOPASSWD で許可しておく。 必要に応じて /usr/bin/rsync も許可しておく。
パッケージを作るとき、パッケージを作っているマシンの環境に依存することは避けたい。 そのために、devtools の中に適当なディレクトリをルートとして Arch の環境を作ってそこに chroot してビルドするツールがある。 手順も wiki に書いてある: DeveloperWiki:Building in a Clean Chroot - ArchWiki。
また、x86_64 な1台のマシンで i686 のパッケージも一緒に作りたい。 なので、自分は ~/chroot-x68_64 と ~/chroot-i686 の2つのディレクトリを用意し、それぞれに対して wiki の解説にあるように
sudo mkarchroot ~archbuild/chroot-x86_64/root base base-devel sudo sudo setarch i686 mkarchroot ~archbuild/chroot-i686/root base base-devel sudo
とした。 そうしてから、それぞれの pacman.conf と makepkg.conf を編集した。 少なくとも makepkg.conf の PACKAGER は書いておくといいと思う。
ビルド
chroot してビルドするには makechrootpkg を使う。 ビルドの前に、chroot 環境のパッケージの状態を最新にするために
sudo arch-nspawn ~archbuild/chroot-x86_64/root pacman -Syu sudo arch-nspawn ~archbuild/chroot-i686/root pacman -Syu
としておく。 そして PKGBUILD のあるディレクトリにいって
sudo makechrootpkg -c -u -r ~/chroot-x86_64/root sudo setarch i686 makechrootpkg -c -u -r ~/chroot-i686/root
とすれば、カレントディレクトリに各アーキテクチャ用のパッケージができあがる。
公開
自分は HTTP サーバとして nginx を利用し、ディレクトリ構成は jaist のを真似た。 まず作ったパッケージはすべて ~/pool というディレクトリに置く。 そして、~/public/myrepo/os/{i686,x86_64} というディレクトリに、~/pool にあるパッケージへの symlink を置く。 nginx では root として ~/public を指定しておく。 最後に、repo-add を使ってパッケージをデータベースに登録する。 そうすると、利用者側は /etc/pacman.conf に
[myrepo] Server = http://example.com/$repo/os/$arch
みたいに書けば利用できるようになる。
以上の公開手順をまとめて、以下のようなスクリプトを使っている。
PKG=$1 REPO=myrepo POOL=$HOME/pool PUBLIC_I686=$HOME/public/$REPO/os/i686 PUBLIC_X86_64=$HOME/public/$REPO/os/x86_64 i686() { ln -s $POOL/$1 $PUBLIC_I686 repo-add $PUBLIC_I686/$REPO.db.tar.gz $POOL/$1 } x86_64() { ln -s $POOL/$1 $PUBLIC_X86_64 repo-add $PUBLIC_X86_64/$REPO.db.tar.gz $POOL/$1 } cp $PKG $POOL case $PKG in *-any.pkg.tar.xz) i686 $PKG x86_64 $PKG ;; *-i686.pkg.tar.xz) i686 $PKG ;; *-x86_64.pkg.tar.xz) x86_64 $PKG ;; esac
補足
自分用にビルドするパッケージの中には、別の自分用にビルドしたパッケージに依存しているものもある。 なので、chroot 環境からもローカルリポジトリとして myrepo を参照したい。 HTTP で公開しているなら利用者側と同じように /etc/pacman.conf に追加してもいいし、 あるいはビルド前に
sudo rsync -avL ~/public/ ~/chroot-i686/root/extra/ sudo rsync -avL ~/public/ ~/chroot-x86_64/root/extra/
とかするようにして、~/chroot-{i686,x86_64}/root/etc/pacman.conf には
[myrepo] Server = file:///extra/$repo/os/$arch
みたい書くようにしてもいいと思う。
テスト補助ライブラリとしての RoboGuice 2.0
そもそも DI というものを知らない自分にとって、Android のテストを書く上で RoboGuice を活用しようと思ったときに、 最初にどう使えばいいのかわからず苦労したので、そのときのまとめ的なもの。
目的
テストを書くときに、あるオブジェクトをテスト用のモックのようなもので置き換えたい。 例えばネットワークアクセスをするようなクラスがあったとき、テスト時には実際にネットワークアクセスするのではなく、ダミーのレスポンスを設定できるようなもので置き換えてテストしたり、 常に失敗するようなもので置き換えて異常系のテストをしたりしたい。
準備
roboguice-2.0.jar と guice-3.0-no_aop.jar と javax.inject-1.jar をメインプロジェクトの libs というディレクトリに入れておく。
メインプロジェクト
こっちの書き方はググればいくつか解説記事や StackOverflow のページが見つかる。 RoboGuice 固有の話は
- アクティビティは
android.app.Activity
ではなくroboguice.activity.RoboActivity
を継承するようにする。 - 使う Module はリソースとして書く。例えば res/values/roboguice.xml に
roboguice_modules
という string-array として Module の完全なクラス名を並べる。
あたりか。
それ以外の DI の話は Guice について調べるといいと思う。 Guice の wiki は絶対に読む必要がある。
テストプロジェクト
テスト時に inject するオブジェクトを変えるには、テスト時に Module を切り替えればいい。 例えばテスト時には FakeModule
を使いたい場合、 ActivityInstrumentationTestCase2
を使ったテストケースでは、setUp()
か各テストケースで getActivity()
を呼ぶ前に
Context ctx = getInstrumentation().getTargetContext(); Application app = (Application) ctx.getApplicationContext(); Module m = Modules.override(RoboGuice.newDefaultRoboModule(app)).with(new FakeModule()); RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE, m);
というような操作を行うことで達成できる。
一方、tearDown()
では
RoboGuice.util.reset();
としておく。
これでだいぶテストが書きやすくなった。 けどそもそも Android のテスト環境は (まだ) 中途半端なもののように見えて、テスト書くのがむずかしい……