2012/05/30

署名Androidアプリケーション作成時のWarning

Google PlayにAndroidアプリケーションを載せる前に、アプリケーションに署名を行ないますが、 Export Signed Application Package...を選択した時に、同一パッケージ内で can't find referenced classのワーニングが大量に発生して、パッケージを作成する事ができませんでした。

いろいろ調べると、proguard設定に-dontwarn設定を追加すれば良いと書かれていますが、 不必要に全てのワーニングを抑制するのが嫌だったのでproguardのサイトを眺めた時のメモです。

ワーニングメッセージの例

...
Proguard returned with error code 1. See console
Warning: com.example.android.MenuFragment$1: can't find referenced class com.example.android.TempActivity
Warning: com.example.android.ToolsFragment: can't find referenced class com.example.android.ImportAsyncFragment
Warning: com.example.android.ToolsImportFragment$1: can't find referenced class com.example.android.ImportAsyncFragment
...

EclipseのADTバージョンによるproguard設定の指定方法の違い

Eclipse 3.6ベースのADTを使っていた時には、project.propertiesにproguard.cfgファイルを指定していました。

...
target=android-4
proguard.config=proguard.cfg...
...

この時のproguard.cfgは必要な設定全体を含んでいましたが、 Android 3.7ベースのADT 18.0.0では、project.propertiesに次のような行が含まれています。

...
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
...

コメントの一部なのでまぎらわしいですが、proguard.configで始まる一行をコメントアウトして、システム全体で共通の設定ファイルを使うように変更されています。

自分のアプリケーション用の設定は、AndroidManifest.xmlなどと同じフォルダにproguard-project.txtを作成し、 差分だけを記述するように変更されています。

Proguard トラブルシューティング記事のまとめ

proguardサイトのトップページから"Manual"をクリックすると、左のメニューが変化して"Troubleshooting"ページにアクセスする事ができるようになります。

can't find referenced classで検索すると、3つの項目が並んでいました。 Eclipseを使ってAndroidアプリを開発している場合には、1,2はなさそうです。

選択肢1. 必要なjarファイルが指定されていない場合

指定漏れのあるjarファイルがあれば、-injarsオプションなどで適宜追加するように書かれています。 コマンドラインでビルドする場合には注意が必要でしょうが、Eclipse内でビルドが正常に完了して、Eclipseからproguardを起動していれば必要ないはずです。

選択肢2. アプリケーションの稼働に不要なクラスの参照関係が解決できない場合

ワーニングに指定されているクラスへの参照は必要ないから、解決できなくても動くよ、そんな場合にはfilter outの指定をするように書かれています。

これは1.との合わせ技で、-injarsで指定するjarファイルの後ろに無視する条件を指定する方法で、これもEclipseを使っていれば本来必要ないはずです。

選択肢3. filter outが気に入らなければ-ignorewarningsか-dontwarnを使えばいいよ

ほとんどのEclipseでAndroidアプリを開発している場合にあてはまるのが、このオプションを指定する場合だと思います。

StackOverflowなんかをみていても、-dontwarnを指定すればいいんじゃない?、というアドバイスが多かったです。

Troubleshootingページをみると、"-dontwarn java.awt.**" みたいな指定ができるので、 自分が作成した同一パッケージ内でのクラスの参照関係が解決できなくても無視するように次のような設定をしています。

現状のproguard-project.txt全体

-keepclassmembers class com.example.android.InfoFragment.DemoJavaScriptInterface {
   public *;
}
-dontwarn com.example.android.*

WebViewを使ってJavaScript用にオブジェクトを設定している場合には、そのクラスを指定するように書かれているので、その下に-dontwarn行を追加しました。

さいごに

署名済みAPKファイルは無事に生成できるようになりました。

別のprogurad.cfgの挙動が変化した分けじゃないんですが、ADTをアップグレードして空のプロジェクトを生成した時にproguard.cfgがなくなったりすると、そのまま昔のproguard.cfgをproguard-project.txちょっとした混乱が起きそうです。

Googleで解決策がみつかるのは良いんですが、そのまま信用して簡単な策を取ると、いろいろ問題がありそうですね。 まぁ趣味の範囲なら、なんでも良いんですけれど。

いろいろな事がボーダーレスになって、プロとアマチュアの境界があいまいになりつつあるのが、少し怖いです。 怖いのはアマチュアに侵食されるんじゃなくて、プロのはずがアマチュアに落ちる事なんですけどね。

2012/05/24

Fragmentを使ったAndroidアプリの作成

いままでの知識と新しい情報

Androidアプリを作っていましたが、画面の切り替えにTabを使おうとしたところで、 Deprecatedクラスを避けるためにFragmentを使うことにしました。

minSdkを低い値にしているとはいえ、TabHost, TabWidgetを使うために標準ライブラリに代替クラスは準備されずに、 Support Librariesを使わなくてはいけないというのは少しおかしな印象を持ちます。 いずれにしても、良い機会なのでFragmentについての資料を読み漁っているところです。

いろいろ新しい仕組みが整いつつあるので、これまでProviderによるNetwork MVCパターンを使ってきた非同期更新の仕組みをLoaderに切り替えたり、ProgressBarで逃げてきたAsyncTaskを管理する処理でViewを持たないFragmentを使うなど、実装をやり直す余地はありそうです。

ただ、いままでの方法は、これはこれで問題もないので、とりあえずはTab表示したいActivityと使い回しをしたいActivityをFragmentに置き換えたり、バックグランドで処理をするのが面倒だったためAsyncTask + ProgressBarで逃げていたところをViewなしFragmentにするところから始めようかなと考えています。

まぁ、ここにきて時間が少しある事もあって、あんざいゆきさんの著書やYoutube講演/資料なんかを中心にAndroid 3.x以降の変更点を調べています。

いままでPlatform 2.3.xまでの知識で、1画面1Activityなアプリを作成してきて満足していたので、Fragmentは面倒そうに感じて避けてきましたが、いまは積極的に使うべきだと思っています。

標準的な枠組みの中で、これまで各自が個別に対応していた処理が行なえるのは、チームで開発する場合などに威力を発揮するはずです。

Fragmentの良いところ

Fragmentを使う事の利点は、明示的にViewを生成して返すonCreateViewメソッドが準備されている事でしょうか。 小さな事ですが、全体の流れやメソッドに一貫性が生まれつつあるように感じています。

とはいえ、単純にActivityをFragmentに変換しただけでは、いろいろ細かいところで適当ではない処理を行なっているので、使いまわせる部品となるようにBundleオブジェクトなどでインタフェースを考えてやらないといけないところがいくつかあります。

これからFragmentPagerAdapterを使ったり、FragmentActivityとFragmentの呼び出し順などライフサイクルをちゃんと理解しないといけないので、しばらくは忙しくできそうです。

2012/05/21

Androidアプリケーション開発で感じたこと

最近は仕事を探していることもあってAndroidでのアプリケーション開発を学ぶ事に時間を費しています。 Eclipseも数年前に比べれば非常に成熟していて、パッケージのアップデートによる相互依存性を解決できずに起動できなくなるような状態には、ほぼならずに安定かつ快適な開発環境を提供してくれています。

Androidアプリケーションの開発に必要なハードルはパソコンと、アプリケーションをGoogle Playで公開するために開発者登録をするクレジットカードくらいで、アプリケーションを開発したいと思えば学生でも気軽に始められるような状況です。

そして開発に必要な情報もまたインターネットで入手する事ができ、StackOverflowなどから情報を引き出す事ができます。

とはいえ、Android 1.6の頃からの情報と3.0以降の情報が錯綜していて、古い情報や制作手法を解説しているページが多い点も気になりました。

情報の入手性と確からしさ

多くの開発者がその経験をまとめた記事をブログにまとめています。 そして、私も含めて、その情報のほとんどは、ある特定のテーマを解説する断片的なものです。

一部にはリファレンスの内容を網羅的に翻訳したり、その上で解説を加えているものもありますが、例外的です。

多くは、(当然のことですが)、特定の操作や目的に対する解決策を1つの記事としてまめています。 例えば、作成した画像ファイルをギャラリーアプリなどに反映させるために、「MediaStoreサービスにデータをinsert()しましょう」といった記事が作成されます。

しかし、同時に管理されている画像ファイルの属性を変更した場合に、アプリでその変更が反映されない事に気がつき、「MediaStoreからレコードを削除して、再度insert()しましょう」といった内容もまた記載されています。

これらは作者の体験として正しいものですし、実際に正しく動きます。 しかし目的を達成するためにはMediaStoreに対して、update()/notifyChange()メソッドを使用しても良いはずです。 システムに対する負荷は、より軽くなるでしょう。

情報としては間違っていないけれど、ベストではない、常に、そういう気がまえで検索エンジンの結果をみる事る事が必要だと、つくづく感じました。

情報の鮮度

GridViewやListViewのパフォーマンスチューニングのために、Googleで検索したいくつかの記事を参考にしましたが、 CursorAdapterの派生クラスを作成したり、O'ReillyのプログラミングAndroidにあるA Network MVCについて触れているものは多くはありませんでした。

本格的なアプリケーションを作成するためには、CursorとCursorAdapterを使い、効率良くViewを描画する事が必要だと感じています。静的な項目をリストすることしかできないために、ArrayListオブジェクトを作ってListViewに渡すようなコードは書きたくありません。

Programming AndroidはSafari Onlineでも読めますし、A Network MVCの項目はお勧めです。 サーチエンジンではフレームワークの解説ばかりで、A Network MVCのCursorとAsyncTaskを使用した非同期更新のパターンを知る事は難しいかもしれません。

開発環境の進化

チューニングのための手法はいろいろ進化していて、Android Platform 3.0以上を対象にEclipseでプロジェクトを作成すれば、ネットワークアクセスしている処理をUIスレッドに記述するだけでANRを避けるためコンパイルに失敗します。

現実にはPlatform 2.3.xを対象にしないHoneycomb以降のみを対象としたアプリを開発する機会は、よほど限定された環境を対象にしない限りは、ないと思います。ネットワークアクセスを行なうアプリケーションを開発していれば、試してみる事をお勧めします。何かAsyncTaskを使っていなかったり、その使い方がまずいところを指摘してくれるかもしれません。

SQLite3とORマッピングオブジェクト

スキーマを記述しない事がスマートだという風潮もありますが、インクリメントアップデートするように構成するなどの工夫をすれば世代管理などの運用はそれほど面倒ではありません。

これを避けるためにORマッピングを使う例もありますが、JavaのReflection APIを使う事でJITの対象から外れ、パフォーマンスダウンを引き起す事は良く知られています。時々使用するメソッドをReflection APIを使って汎用的に実装する事は良いかもしれませんが、ループの中で使う事は避けるべきで、あまり突き詰めずに、ほどほどにするのが正解です。

これまでの経験ではテーブル定義をJavaBeans的にGetter/Setterアクセスするクラスを作成して、適切なコンストラクタとinsert,update(save),restoreぐらいのDBアクセス用のメソッドを個別に実装してあげれば十分だと思っています。 参照のみであれば直接CursorオブジェクトをCursorAdapterでレンダリングに使い、書き換えが不要なレコードを表現するbeanの使用は避けましょう。

DBへのアクセスは局所的に抑え込むため、ContentProviderと、これらのプレースホルダ的なクラス以外から、直接DBアクセスをするような処理はまずい場合が多いと思います。

またSQLite3に関連する処理の説明はいろいろありますが、サンプル的なものが多く、スキーマの更新のような運用性 を考慮していない記述はお勧めできません。

機会があれば、SQLite3の使い方は別にまとめておきたいと思います。

最適化手法の変化

JITが有効になったPlatform 2.2以降と、それ以前では対応に違いがでてきます。 例えばインタフェース型の変数にオブジェクトを格納し、メソッドをコールすると2倍遅い場合があると言われていますが、それはJITを搭載していないプラットフォームでの話しです。JIT搭載環境では6%ぐらいだという記述があります。

最近ではPlatform 2.2以降を対象としても、問題とならないケースもあるでしょう。 反対に1.6以降で対応しなければいけないケースもあり、ハードウェアのスペックもより厳しい事が想定されます。

様々なプラットフォームに合わせて、その最適化手法の優先順位が変わってくる事は知っておくべきです。

適切なスタティックメソッドの実装やクラス内部からのgetter/setterコールを防ぐ事は必要だと思います。 しかしパフォーマンスのために、瑣末なコーディングルールにこだわるよりも、アプリケーションの内部構造とUXの向上にもっと注力しましょう。

この記事で取り上げた品々

2012/05/20

Apache Commons Imaging (a.k.a. Sanselan)によるExifヘッダを編集する Androidアプリの作成

Apache CommonsのCommons Sanselanは 画像処理を行なうためのPure-Java実装ですが、2009年以降は安定版がリリースされていません。

しかしSVNのリポジトリをみると、Sanselan(org.apache.commons.sanselan)からImaging(org.apache.commons.imaging)へと名前とパッケージ名をかえて、開発が進んでいるようです。

AndroidではSanselanでも問題ないようですが、別件でTiffファイルのヘッダがうまくパースできなかったので、Commons Imagingを使う事にしました。Sanselanを使ったアプリケーションはいくつかありますが、Commons Imagingを使ったサンプルはあまりないようです。

今回はSanselanとImagingを比較しながら、使い方のメモを残していきます。

SanselanによるExif日付情報の書き換え

Sanselan自体にはExifの日時情報を書き換えるような適当なサンプルがないので、Googleで探すとGPSdingsのSanselanExifWriter.java、openstreetmapのExifGPSTagger.javaなどのコードをみる事ができます。

ちなみに、これらのコードはGPLv2, GPLv3で配布されています。

ExifGPSTagger.java でのSanselanを使ったコード例 (一部変更)

  Double[] timeStamp = {
    new Double(calendar.get(Calendar.HOUR_OF_DAY)),
    new Double(calendar.get(Calendar.MINUTE)), 
    new Double(calendar.get(Calendar.SECOND))
  };
  TiffOutputField gpsTimeStamp = TiffOutputField.create(
    GPSTagConstants.GPS_TAG_GPS_TIME_STAMP, outputSet.byteOrder, timeStamp);

SanselanではTiffOutputFieldオブジェクトを作っていきますが、Commons Imagingと比べると編集対象によって値の指定方法が異なる点はすこし扱いずらいと感じました。

Commons ImagingによるExif日付情報の書き換え

SVNからコードをcheckoutすると、パッケージ名が org.apache.commons.imaging に変更されています。 これはスナップショットですが、今回は2012/05/19にチェックアウトしたコードを使っていきます。

内部構造も変更が進んでいるので、単純にパッケージ名を変更するだけでは前述のコードは動きません。 先ほどと同じ内容のコードは次のようになります。

  TiffOutputDirectory gpsDirectory = outputSet.getGPSDirectory();
  gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP);
  gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP,
      RationalNumberUtilities.getRationalNumber(new Double(calendar.get(Calendar.HOUR_OF_DAY))),
      RationalNumberUtilities.getRationalNumber(new Double(calendar.get(Calendar.MINUTE))),
      RationalNumberUtilities.getRationalNumber(new Double(calendar.get(Calendar.SECOND))));

SanselanではTiffOutputFieldを作成して追加していましたが、Commons Imagingでは直接TiffOutputDirectoryのaddメソッドを使うように変更されています。

また org.apache.commons.imaging.common パッケージの RationalNumberUtilities を使い、Double型をRationalNumber型に変換するところもポイントです。

全体としては操作方法に統一性があるのでAPIドキュメントとfind, grepがあれば、必要な機能の使い方は推測できると思います。

AndroidでCommons Imagingを使う時の考慮点

パッケージのサイズが大きいので、SanselanのExif編集機能だけをAndroid用にまとめたコードsanselandroidとしてgoogle codeでホストされています。

スナップショットをビルドしてできるライブラリJARファイルは672KBなので、Sanselan-0.97と比較して200KB近く大きくなっています。 そこでsanselandoroidにならってEclipse上でライブラリプロジェクトを作成し、Androidアプリから参照するようにしました。

まだJARファイルのサイズは388KBほどあって、sanselandroidほど小さく(248KB)なっていませんが、うまくまとまればGithubにでも公開したいと思います。需要があるのが分かれば、現状のまま上げてもいいんですけどね。 とりあえずはSanselandroidで十分だと思います。

さいごに

AndroidにはExifInterfaceクラスがあって、そこそこの操作はできますが、書き換えについてはヘッダを壊してしまう場合があるように思えます。

実際の操作はandroidに組み込まれているjheadライブラリを使っているはずです。 少ないファイルを処理している範囲では、体感的なスピードはSanselanでも大差はないと感じています。

Android 2.2のソースコードではjheadのバージョンが2.87です。 そこからの変更点をみていくと、いくつか重要なfixが行なわれているようにみえるのが気になるところです。

2012/05/07

SEが陥ってはいけない矛盾

親しくしている人のノートPCが壊れてしまったので、手元にあったThinkPad X61に Windows XP Professional + MS Office 2010を入れて貸しているのですが、 その後の調子を聞いた時にOffice 2010のリボンインタフェースが使いずらいと言っていました。

職場でもWindows XPとWindows7が入り乱れている環境が増えてきているのではないでしょうか。

リボンインタフェースについては、否定的な意見を良く聞きます。 個人的には気にいっていますが、開発者が「昔のあれは良かった、今のこれはだめだ」、そんな事を言うのは自由です。 ただ自分は新しい状況に積極的に対応していないのに、 「あの人は試しもしないで質問ばかりしてくる。いつまでたってもパソコンが使えるようにならない」そんな事をいうのは、人に迷惑はかけていないというのかもしれませんが、それでも、矛盾しているのではないでしょうか。

これを機会に、開発者ではない一般のユーザーがOSの変化やIE6からIE9への移行といった軽微な変化にも戸惑ってしまう理由を考えてみると良いのではないかなと思いました。

非難するのは簡単

パソコンを使いこなせない人を指差して、「学習意欲が低いとか」、「試しもしないで質問ばかりする」、そういう事をいう人をたまに出会います。

確かにパソコンがうまく使えない人は、自分の時間を割いて必要とされるスキルを身に付けようという意欲には乏しい事が多いとは思います。

しかし、そういう能力も意欲も高くない人をさらに落し入れるのが、ちょっとしたUIや使い勝手の悪さという点は、自分の経験からも答えが出せる事だと思います。

自分の経験から、解決策を考えてみる

そもそも意欲の低い人に対する対応方法はありません。適切なインセンティブと評価を与える以外にできる事はほとんどありません。

覚えておいて欲しいのは、賞罰は必要ではなく、評価が必要なのだという点です。

工場のライン管理では、チームとメンバーを書いた一覧表を休憩室などに張り出しておき、その週や月毎の一番良い評価を得たチームにシールを張っていく、そんな事で大きな改善効果が得られたという経験談を聞いたことがあります。

故事にもあるように、生きるために水が必要な馬を、水場に連れていく事はできますが、強制的に水を飲ませる事は、まずできません。 そもそも無理に何かをさせるという行為には恒久的な効果は期待できません。

それでも、同じ質問は2回まで、メモを取る、そういったルールを設ける事はとても重要です。 それでうまく出来なければ対応を考えればいいし、大切な事はより上位のリーダーが全体のルールを決定し、全体に周知するという事です。

仕事上では失敗が許されないため、自宅でシステムファイルを消してしまった開発者は多くを学べますが、仕事ではそんな行為をした日には罰を与えられるだけで、同じ行為でも評価・効果はまったく違ってきます。

失敗できる環境、試せる環境、そういう環境を準備して、「あとは個々の意欲次第」といえる環境を与えられているか考えるべきでしょう。

非難するのは間違い

人間の行動を分析する時には、虚栄心よりも、恐怖に基づいて説明するとうまくいくケースが多くあると感じています。

仕事で失敗をする事はできません。 その現実は、人の意欲を低め、積極的な経験を積む機会を奪っているという事はできないでしょうか。

失敗しても大丈夫な環境を作り、提供する事は、教育のためにはとても重要です。

それでも、何も自発的に行動しないのであれば、その点を指摘するべきです。

そもそもパソコンの電源ボタンがどこにあるか分からない人に、失敗しても良い環境を作る事などできないのです。 ネットカフェを実験環境にして、OSのシステムフォルダを消してみる、そんな休日の過し方がまともだと私は考えています。

Ubuntu 12.04 LTS 64bit版で、JACKとflashplayerを動かしてみた

カーネルをPREEMPTに変更したタイミングで音楽の再生にjackdを使ってみました。

立ち上げ時の初期設定が少々面倒ですが、一日のほとんどの時間でUbuntu 12.04 LTSを使う現状では、 入力ソースをコントロールできるJACK Mixer等は本当に便利です。

便利なのですが、JACKを使う事で、それまで特に設定していなかったfirefoxやgoogle-chrome上の flashplayerからの音が出なくなってしまいました。

それだけなら我慢できたのですが、音声出力がブロックされるためか動画の再生もうまくできなくなり、 flashplayerに引きずられる形でブラウザ全体が不安定になってしまいました。

JACKを動かしながらflashplayerからの出力もできるようにするために、 ALSA/PulseAudioからjackdに接続するための方法を探ったところ、Ubuntu Japanese Team Wikiに JACKとPulseAudioを併用するための方法がまとまっているのを見つけました。

Japanese Teamの文書に従えば問題ないですが、つまづいたところだけ補完しておきます。

基本的な考え方

Linux上で音声を扱うAPIはいくつかありますが、普段は特に意識することなく使っていると思います。

普段意識せずに使っているもの(ALSA/PulseAudio)をJACKというもので置き換えるわけで、 普通にしていれば使えたものが突然使えなくなる原因を生むわけです。

世の中にはJACKよりも、デフォルトでサポートされる事の多いALSA/PulseAudioに対応しているアプリケーションが当然多いので、そういうアプリケーションの音声出力をJACKに流す方法を取る事にします。

うまく行かなかった方法

まず、ALSAからJACKに音声出力を流す方法はうまく行きませんでした。 これはPulseAudioを使う事にして解決しました。

次にlibflashsupport-jackをコンパイルする方法も見つかりましたが、少し古いソースコードだった点と、 64bit環境でflashplayer用に共有ライブラリを作るのは良いアイデアに思えなかったので試しませんでした。

Linuxで音を扱う方法は複雑

そもそもLinuxで音声出力を行なうにはカーネルレベルではOSS/ALSAが存在していて、各種デバイス(スピーカー)を制御しています。

そこに各アプリケーションから直接音を出してもいいけれど、抽象化したり高度なミキサー機能など、いろいろな付加機能を付け加えるためにPulseAudioやJACKなどが存在します。

今回問題になったのは単純にJACKを動かしている環境では、ALSAをサポートするアプリケーションはそのままでは動かないという点です。

ALSAからJACKに出力するために、~/.asoundrcを作成する方法もあるはずなのですが、今回はこれがうまく動かなかったので、PulseAudioを使う事にしました。

設定手順どおりに行かなかったところ

ほとんどの環境ではUbuntu Japanese Team Wikiにある JACKとPulseAudioを併用するための方法で対応できると思います。

ただ昔からアップグレードしてきたシステムなので、~/.asoundrcが悪さをして音が出力されませんでした。

ファイルを削除する $ rm ~/.asoundrc だけでうまく行きました。 ただpavucontroを起動するだけではうまく行かずに、手順にある画像のようにJack sinkを明示的に選択する必要がありました。

JACKを扱う場合にはQJackCtlでのConnections設定が不可欠で、起動しただけでは音が出ないのは普通のことだと思って、頑張って下さい。Patchbayを使って設定を保存できるようになると手間がだんだん省けて、手に馴染んでいくでしょう。

Patchbayで、OutputとInputを接続してもsystemにもパッチされてしまう場合には、output側の設定でExclusiveにチェックを入れておくとsystemには接続されないようになります。

まとめ

最終的に環境が動きだすと、firefoxやgoogle-chrome, chromiumなどのブラウザでflashplayerを使う場合に、各flashplayer毎にpavucontroに項目が増えます。

これをミキサーにして扱う事で、JACKプラグインを設定しなくても音が出るようになり、アプリケーションが安定的に稼働するようになりました。

大抵はJACKプラグインがある場合には、直接JACKに接続した方が良いと思っています。 しかしUbuntu 12.04 LTSのVLCの出力にはJACKプラグインもありますが、音が断続的に出力されるようになります。 VLCではALSA出力に切り替えてPulseAudio経由で出力するようにしたところ問題なく聞こえるようになりました。

VLCのJACKプラグインがうまく動かない理由は不明ですが、そういう例もあるという事でJACKとPulseAudioが動く事で選択肢が広くなって便利になったと思います。

2012/05/05

Debian WheezyでCouchDB 1.2.0をコンパイルした時にはまった事

CouchDB 1.2.0をさくらVPSで稼働しているDebian wheezyに導入したのですが、 エラーがでてcouchjsが実行できない自体になった時のトラブルシュートのログです。

エラーの内容

couchdb自体は問題なく起動したものの、viewを参照するとvar/log/couchdb/couch.logに次のようなエラーが大量に書き込まれていました。

...
[Wed, 02 May 2012 06:30:37 GMT] [error] [<0.4852.0>] OS Process Error <0.5474.0> :: {os_process_error,
                                                     {exit_status,139}}
[Wed, 02 May 2012 06:30:37 GMT] [error] [<0.4852.0>] OS Process Error <0.5477.0> :: {os_process_error,
                                                     {exit_status,139}}
...

原因はSpiderMonkeyのライブラリバージョンにあったのですが、 突き止めるまでに時間が少しかかってしまいました。

Debian wheezyで利用可能なSpiderMonkeyのバージョン

このバージョンのDebianでは2種類のライブラリが使用可能です。

  • libmozjs-dev - Development files for the Mozilla SpiderMonkey JavaScript library
  • libmozjs10d - Mozilla SpiderMonkey JavaScript library
  • libmozjs185-1.0 - Spidermonkey javascript engine
  • libmozjs185-dev - Spidermonkey javascript library - development headers

先頭2つのlibmozjs-devとlibmozjs10dはDebianプロジェクトが開発するfirefox互換ブラウザに組み込まれているJavaScriptエンジンで基本的にはSpiderMonkeyと同一ですが、Mozillaプロジェクトが配布しているSpiderMonkeyのバージョンは1.8.xとなっています。

このライブラリを導入した状態でCouchDBのconfigureを走らせると、バージョンが新し過ぎてサポートされていない旨が表示されます。

そこでlibmozjs185-1.0とlibmozjs185-devを導入して、configureが問題なく動くようにしたのですが、これが問題を引き起した原因となりました。

configureの振舞い

先ほどリストした全てのパッケージが入った状態では、configureは問題なく完了します。

この状態ではcouchjsコマンドだけが、libmozjsとリンクされてしまいます。 正しいのはlibmozj185とリンクされている状態ですが、lddでみるとlibmozjs10dパッケージに含まれるライブラリとリンクされています。

$ ldd src/couchdb/priv/couchjs |grep js
	libmozjs.so.10d => /usr/lib/libmozjs.so.10d (0x00007fab300d0000)

まぁconfigureのバグなんですが、このお陰でviewにアクセスした時だけ結果が正しく帰ってこない事になります。

couchjsコマンドのデバッグ方法

で、原因を突き止めるために、簡単なJavaScriptで書かれたスクリプト(a.js)を準備して実行してみます。

a.jsスクリプトファイル

print("test");
 $ /usr/local/bin/couchjs a.js
Segmentation fault

どこで落ちたのか原因を探るためにstraceを使う事が多いのですが、今回はまったく役に立ちませんでした。

結局のところgdbを使ってlibmozjs.soライブラリの中で落ちている事を確認しました。

 $ gdb /usr/local/bin/couchjs
 (gdb) run a.js
 (gdb) backtrace
(gdb) backtrace 
#0  0x00000000004049e5 in ?? ()
#1  0x00007ffff76d5ab8 in ?? () from /usr/lib/libmozjs.so.10d
#2  0x00007ffff764c937 in JS_ExecuteScript () from /usr/lib/libmozjs.so.10d
#3  0x000000000040227c in main (argc=<optimized out>, argv=<optimized out>)
    at couch_js/sm185.c:389

これで無事に/usr/lib/libmozjs.so.10dの中の処理で落ちていて、本来リンクして欲しくないライブラリを参照していた事が原因だとわかりました。

回避策

この問題を避けるためには、毎回正しいlibmozjs関連のファイルをポイントするようにconfigureオプションを指定する他ありません。

幸い、couchdb 1.2.0もdebian wheezyも比較的新しいパッケージなので、昔のcouchdbのようにinclude fileから指定する必要はなくなっています。

configureを次のように実行して、libmozjs185.soをリンクしている事を確認します。

./configure --with-js-lib-name=mozjs185
checking for JS185... yes
checking for JS185... yes
checking jsapi.h usability... yes
checking jsapi.h presence... yes
checking for jsapi.h... yes
checking for JS_NewObject in -lmozjs185... yes

これでlibmozjs10dパッケージが導入されていても、エラーなくcouchdbが動きます。

さいごに

configureの中でヘッダーファイルのチェックでpkg-configを使っているのに、ライブラリのチェックでは実際にコンパイルできるか普通にチェックしてしまっているので、libmozjs.so.10dをリンクしてしまっていました。

これはバグだとは思うのですが、pkg-configにlibmoz185パッケージが入っているとは限りませんし、どちらもECMAScript Ver.5をサポートするようにできているので、関数のチェックを確実に行なうのは難しそうに思えます。

CouchDBの経験があればcouchjsが動かないだけで、libmozjs周りを見に行くとは思うのですが、今回は現象だけをみても原因がよく分からなかったために時間を数時間ロスしてしまいました。

まだtestingフェーズのdebian wheezyを使っている人が多いとは思いませんし、そんな人達は自力で解決しちゃうと思いますが、どなたかの役に立てば幸いです。

2012/05/02

さくらVPSのDebian wheezyにCouchDB 1.2.0をインストールしてみた

少し時間ができたのでCouchDB 1.0の環境を1.2にアップグレードする事にしました。

手製ツールの稼働検証やら、DTIのVPSに作った郵便番号検索の仕組みを更新するとか、1.0から1.2への変化をキャッチアップするだけで、何だかぐったりするような作業で腰が重かったのですが、この機会に(現実逃避を兼ねて)とりかかることにしました。

私のcouchdbの使い方はバックエンドサーバーとして使うものなので、フロントエンドにcouchdbを使おうとする普通の使い方をする方はSSLなどのセキュリティ設定にもっと注力する必要がある事に

進め方の方針

アップグレードとはいっても現状の環境は止めるわけにいかないので、さくらのVPSに構築したtestingステータスのDebian wheezy上に新規にEarlang, CouchDB、その他の自前ツール等々をインストールをして、切り替えることにしました。

最終的にはCNAMEで設定しているwww.yadiary.netのレコードを書き換える事で対応する予定です。

対象の環境

現行のDTIのプランは490円のCPU: shared 1core, Memory: 256MB (max. 1GB)の構成で、 スペックをみる限りはリッチですが、このアプリケーションの性能にはほとんど変化ないだろうと考えています。

さくらVPSに構築しようとしている環境は次の通りです。

  • VPSプラン: さくらのVPS 2G (CPU: shared 3core, Memory: 2GB)
  • OS: Debian wheezy (次期stable、現testing), 64bit (x86_64)
  • Earlang: OTP-R15B01
  • CouchDB: 1.2.0

ちなみにDTIにある現行機は、Debian squeezeで、Earlang/OTP-R14B, CouchDB-1.0.2という構成です。

CouchDB 1.0系と1.2系の違いについて

1.1での変更も含めた完全なリストは apache-couchdb-1.2.0.tar.gz に含まれるCHANGESファイルにあります。

Native SSLサポートが含まれていたり、細かい点ではセキュリティ上の改善点もいくつか含まれています。

インストール手順

基本的な進め方はCouchDB: 1.0.1から1.0.2のリリースアップ手順 (xstow対応版)に従って進めていきます。

バージョンアップする時には/usr/local/etc以下に配置してある設定ファイルを退避して、新しいバージョンの設定ファイルをコピー、編集する事になりますが、1.2.0でも進め方に違いはありませんでした。

今回もxstowを使って/usr/local以下にインストールするため、/usr/local/stow以下にインストールしたcouchdb-1.2.0の直下からvarディレクトリは/usr/local/var以下に移動しています。 さらにスクリプトファイルから/usr/local/stow/couchdb-1.2.0/varを参照している個所を修正します。

確認するべき対象は次のコマンドで確認しました。

$ find . -type f | while read file ; do grep 1.2.0/var $file > /dev/null 2>&1 && echo $file ;  done

以前はetcファイルも/usr/local/etcに直接展開していましたが、今回は止めました。 バージョンアップの頻度は思ったよりも多くはなくて、その度にlocal.iniをコピーしても手間ではなさそうですから。

VPSサーバでの稼働検証

普通にサーバーを立ち上げるとIPv4のloopbackアドレス(127.0.0.1)にバインドします。 手元にないサーバーへ接続する場合に、グローバルアドレスにいきなりバインドすると外部に対して無防備になってしまいます。

一般的にサービス前のサーバーに接続するために、いくつかの方法が考えられます。

稼働確認という意味では、sshが一番簡単だろうと思います。 サービスイン後も使い続けるのであれば、stunnelがお勧めです。

ただWebサーバーのVirtualHostや、SSLポートのように、アクセス時に指定するサーバー名も重要な要素となる場合には、そうも簡単にはいきません。 今後のためにはiptablesを使った方法も、見当しておく必要があります。

というわけで、今回はiptables/ip6tablesを使ってグローバルアドレスにバインドさせてみる事にします。

iptablesを使った接続制御

iptables/ip6tablesを使う場合には、既にあるルールを汚染せずに追加、削除を行なう必要があります。 ここではポート番号を5985にして、このポート番号に対する制御は事前にされていない前提で作業を行ないます。

local.iniファイルの編集

IPv6アドレスを使っていますが、IPv4アドレスを使う場合には、アドレスの他にip6tablesをiptablesに読み替えてください。

/usr/local/etc/couchdb/local.iniファイルの編集個所

port = 5985
bind_address = 2001:xxxx:31d5:87d3::1
サーバー起動前:iptablesへのルール追加

デフォルトのINPUTルールが、ACCEPTでなければ2行目は不要ですが、明示的に1行目で指定した以外のIPv6アドレスからの接続を拒否しています。

 $ sudo ip6tables -A INPUT -p tcp --dport 5985 -s 2001:3e0:a32:xxxx:8d45:678a:2fe8:xxxx -j ACCEPT
 $ sudo ip6tables -A INPUT -p tcp --dport 5985 -j DROP 

稼働確認

サーバー上の設定が反映されている事を確認します。

 $ sudo ip6tables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
ACCEPT     tcp      2001:3e0:a31:1:7d44:6780:2fd9:ca43/128  anywhere             tcp dpt:5985
DROP       tcp      anywhere             anywhere             tcp dpt:5985

設定が反映されていれば、サーバーを起動します。

 $ sudo /usr/local/etc/init.d/couchdb start

これでWebサーバからアクセスする事ができます。 IPv6アドレスを使う場合にはURLが http://[2001:3e0:a32:xxxx:8d45:678a:2fe8:xxxx]:5985/_utils/のように[]で囲む点に注意が必要です。

で、いろいろ作業が終ったらサーバを停止しておきます。

 $ sudo /usr/local/etc/init.d/couchdb stop
サーバー停止後:iptablesルールの削除

偶然サーバーが起動してしまう可能性もありますが、将来的にこの方法を使う事はないので、ip6tablesルールを削除しておきます。

 $ sudo ip6tables -D INPUT -p tcp --dport 5985 -s 2001:3e0:a32:xxxx:8d45:678a:2fe8:xxxx -j ACCEPT
 $ sudo ip6tables -D INPUT -p tcp --dport 5985 -j DROP

作業が終ったところで、 $ sudo ip6tables -L で追加したルールが残っていない事を確認します。

まとめ

とりあえず、これで動くところまでは確認できました。 ここからはいままで作ってきたパッチを1.2に対応させていきます。

参考文献

CouchDBについては公式Webサイトは宣伝要素が強いので、CouchDB Wikiがより実践的で、役に立つと思います。