pyinstaller で wxpython を使用した python コードを Linux 向けにバイナリ化する

結構はまったので、メモ代わりに書いておく。

  • まず最初に、pyinstaller とは
    • python のコードをバイナリ化してくれるプログラム(Unix系、Windows、ともに対応している)
    • 他にも似たようなものとして freeze(Unix系のみ)、py2exe(Windowsのみ)などが存在する
  • Linux 向けバイナリ作成時の基本的な使用方法

ほぼ README.txt に書いてある内容だが、個人的に 1 ファイルのバイナリにしたいので「Makespec.py」に「-F」を付けている点が異なる。「-F」をつけない場合、複数のバイナリファイルが作成され、それらを配布する必要がある。

$ cd source/linux #初回のみ実行する
$ python ./Make.py #初回のみ実行する
$ make #初回のみ実行する
$ cd ../../ #初回のみ実行する
$ python Configure.py #初回のみ実行する

$ python Makespec.py -F /path/to/yourscript.py #ここで「/path/to/yourscript.spec」が作成される
$ python Build.py /path/to/yourscript.spec #ここで「/path/to/yourscript」が作成される

通常は、この手順で「/path/to/yourscript」にバイナリが作成され、実行できるようになる。
だが、おそらく wxpython のような python で記述されていないライブラリを使用しているプログラムの場合、

ImportError: libwx_gtk2u_richtext-2.8.so.0: cannot open shared object file: No such file or directory

などのようなエラーが出力されて実行できない。(この辺は python コードの内容により変わるかも)

    • wxpython を利用した python コードから Linux 向けバイナリを作成する方法
      • 「$ python Makespec.py -F /path/to/yourscript.py」実行後に「/path/to/yourscript.spec」を編集して libwx_gtk2u_* のライブラリを含むように設定する

$ cd source/linux #初回のみ実行する
$ python ./Make.py #初回のみ実行する
$ make #初回のみ実行する
$ cd ../../ #初回のみ実行する
$ python Configure.py #初回のみ実行する

$ python Makespec.py -F /path/to/yourscript.py #ここで「/path/to/yourscript.spec」が作成される
$ vim /path/to/yourscript.spec #ここで libwx_gtk2u_* のライブラリを含むように設定する
$ python Build.py /path/to/yourscript.spec #ここで「/path/to/yourscript」が作成される

      • 「/path/to/yourscript.spec」の書き方

http://pyinstaller.hpcf.upr.edu/docs/Manual_v1.1.html#toc-class-table-of-contents
にあるように、作成するバイナリに別途共有ライブラリを追加したい場合には以下のような書式になる。

collect = COLLECT(a.binaries +
[('readme', '/my/project/readme', 'DATA')], ...)

具体的には、libwx というディレクトリに「libwx_gtk2u_*」のライブラリを置いた場合、spec ファイル内の

exe = EXE( pyz,
a.scripts,
a.binaries,
name='yourscript',
debug=False,
strip=False,
upx=False,
console=1 )

の箇所を以下のように変更する。(ファイル名に付いているバージョンなどは各環境で異なってくると思います)

exe = EXE( pyz,
a.scripts,
a.binaries + [('libwx_gtk2u_qa-2.8.so.0.5.0','libwx/libwx_gtk2u_qa-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_adv-2.8.so.0.5.0','libwx/libwx_gtk2u_adv-2.8.so.0.5.0','BINARY')] + [('libwx_baseu-2.8.so.0','libwx/libwx_baseu-2.8.so.0','BINARY')] + [('libwx_gtk2u_media-2.8.so.0.5.0','libwx/libwx_gtk2u_media-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_media-2.8.so.0','libwx/libwx_gtk2u_media-2.8.so.0','BINARY')] + [('libwx_gtk2u_core-2.8.so.0','libwx/libwx_gtk2u_core-2.8.so.0','BINARY')] + [('libwx_gtk2u_xrc-2.8.so.0','libwx/libwx_gtk2u_xrc-2.8.so.0','BINARY')] + [('libwx_gtk2u_aui-2.8.so.0','libwx/libwx_gtk2u_aui-2.8.so.0','BINARY')] + [('libwx_gtk2u_svg-2.8.so.0','libwx/libwx_gtk2u_svg-2.8.so.0','BINARY')] + [('libwx_gtk2u_ogl-2.8.so.0.5.0','libwx/libwx_gtk2u_ogl-2.8.so.0.5.0','BINARY')] + [('libwx_baseu_xml-2.8.so.0','libwx/libwx_baseu_xml-2.8.so.0','BINARY')] + [('libwx_gtk2u_gizmos_xrc-2.8.so.0','libwx/libwx_gtk2u_gizmos_xrc-2.8.so.0','BINARY')] + [('libwx_gtk2u_core-2.8.so.0.5.0','libwx/libwx_gtk2u_core-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_aui-2.8.so.0.5.0','libwx/libwx_gtk2u_aui-2.8.so.0.5.0','BINARY')] + [('libwx_baseu-2.8.so.0.5.0','libwx/libwx_baseu-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_gl-2.8.so.0','libwx/libwx_gtk2u_gl-2.8.so.0','BINARY')] + [('libwx_gtk2u_ogl-2.8.so.0','libwx/libwx_gtk2u_ogl-2.8.so.0','BINARY')] + [('libwx_gtk2u_gizmos_xrc-2.8.so.0.5.0','libwx/libwx_gtk2u_gizmos_xrc-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_gizmos-2.8.so.0','libwx/libwx_gtk2u_gizmos-2.8.so.0','BINARY')] + [('libwx_baseu_net-2.8.so.0.5.0','libwx/libwx_baseu_net-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_stc-2.8.so.0.5.0','libwx/libwx_gtk2u_stc-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_adv-2.8.so.0','libwx/libwx_gtk2u_adv-2.8.so.0','BINARY')] + [('libwx_gtk2u_gizmos-2.8.so.0.5.0','libwx/libwx_gtk2u_gizmos-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_richtext-2.8.so.0','libwx/libwx_gtk2u_richtext-2.8.so.0','BINARY')] + [('libwx_gtk2u_gl-2.8.so.0.5.0','libwx/libwx_gtk2u_gl-2.8.so.0.5.0','BINARY')] + [('libwx_baseu_net-2.8.so.0','libwx/libwx_baseu_net-2.8.so.0','BINARY')] + [('libwx_baseu_xml-2.8.so.0.5.0','libwx/libwx_baseu_xml-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_richtext-2.8.so.0.5.0','libwx/libwx_gtk2u_richtext-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_svg-2.8.so.0.5.0','libwx/libwx_gtk2u_svg-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_html-2.8.so.0.5.0','libwx/libwx_gtk2u_html-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_xrc-2.8.so.0.5.0','libwx/libwx_gtk2u_xrc-2.8.so.0.5.0','BINARY')] + [('libwx_gtk2u_html-2.8.so.0','libwx/libwx_gtk2u_html-2.8.so.0','BINARY')] + [('libwx_gtk2u_stc-2.8.so.0','libwx/libwx_gtk2u_stc-2.8.so.0','BINARY')] + [('libwx_gtk2u_qa-2.8.so.0','libwx/libwx_gtk2u_qa-2.8.so.0','BINARY')],
name='yourscript',
debug=False,
strip=False,
upx=False,
console=1 )

とりあえず、これで 1 つのファイルにバイナリをまとめることができました。
ただ、60 MB くらいのファイルサイズになり、起動時間も多少重くなった感じだったので、あまり使い勝手は良くないかもしれません。

他のライブラリに関しても、環境によってはライブラリが存在しないという現象が出てくるかもしれません。
そのときには同様にライブラリ追加の記述をしてやることで回避できると思います。

「strace yourscript」のように、「strace」を利用すると、「open()」されているライブラリファイルを調査できるので、どのライブラリをバイナリに含めるべきかヒントが得られるかと思います。

ただ、この設定ファイルの記述方法はライブラリファイルが増えた場合に不便です。
もっと良い方法があるのかもしれませんが、自分の調査した範囲ではこれが限界でした。

今日は少し gccアセンブリ言語(gas?)を学習。
gcc -S something.c -o something.s
アセンブリ言語が hello.s に吐かれる。
as -o something.o something.s
コンパイル
gcc -o something something.o
でリンク。

objdump -d something
でバイナリから逆アセンブル

以下のように素手アセンブルできるコードも書ける。(コメントは # で記述)

.STR0:
        .string "hello"
.globl main
main:
        mov     $1, %ebx      #ebx=1
        mov     $.STR0, %ecx  #ecx="hello"
        mov     $5, %edx      #edx=5
        mov     $4, %eax      #sys_write(ebx,ecx,edx)
        int     $0x80         #exec sys_write(ebx,ecx,edx)

        mov     $0, %ebx      #ebx=0
        mov     $1, %eax      #sys_exit(ebx)
        int     $0x80         #exec sys_exit(ebx)

eaxに数値を代入することにより使用できるシステムコールは /usr/include/asm/unistd.h(i386 の場合、/usr/include/asm-i386/unistd.h)から参照できる。
例えば、i386 環境で「mov $4, %eax」は /usr/include/asm/unistd.h の 4 に相当するもの、つまり sys_write になる。
sys_write の引数は第1引数から順に ebx,ecx,edx と割り当たっている。
「int $0x80」で eax に入れたシステムコールが実行される。

manual は「man 2 write」のように「sys_write」から「sys_」を取り除いて man コマンドを実行して参照可能。

nasm の本は沢山存在するけど、gas の情報ってどこにあるんでしょ。

dv4l に解像度指定できる機能を追加してみた


dv4l は DV カメラの入力を v4l に変換してくれる Linux 向けのソフトです。
これを使用することで ustream でのストリーミングに DV カメラを使用できるようになります。
ただ、DV カメラからの入力である 640x480 のうち、DV カメラの中心部分 320x240 しか ustream に映らないという状態になる問題があります。(画面の周辺部分が切り取られてしまって見えなくなっているイメージ)
今回 dv4l の dv4lstart に、解像度を指定すれば縮小してくれる機能を追加しました。


ダウンロードはこちらから。
http://drop.io/dv4l_ForceResizeAdded


dv4l-1.0-ForceResizeAdded.tar.gz を展開してから

$ ./configure
$ make
$ sudo make install
$ dv4lstart --width 320 --height 240 epiphany

とかやると「--width 320 --height 240」で設定したサイズに画像が縮小された状態の入力が epiphany (ブラウザ)に渡ります。この2つのオプションが今回追加したものになります。




ustream で画面の中心しか映らずこんな感じだったのが


こんな感じになります。(DV カメラの位置は変えてません)




関係ないけど、epiphany は軽いので ustream するときだけブラウザとしてこれを使用してます。
あと、dv4l のコードはインデントがタブとスペースごちゃ混ぜだったので、今回のファイルではそれもスペースに書き直してしまってます。
タブスペース以外の diff は以下になります。

diff -uNrp dv4l-1.0_org_cleaned/interdv4l.c dv4l-1.0-ForceResizeAdded/interdv4l.c
--- dv4l-1.0_org_cleaned/interdv4l.c	2008-11-27 01:41:01.000000000 +0900
+++ dv4l-1.0-ForceResizeAdded/interdv4l.c	2008-11-28 02:04:22.000000000 +0900
@@ -89,6 +89,8 @@ typedef struct {
 } vid_context_t;
 
 static vid_context_t vctx = { 0 };
+static int vid_resize_width = 0;
+static int vid_resize_height = 0;
 
 static int is_videodev(const char *name);
 
@@ -391,6 +393,17 @@ char *getenv(const char *name)
 
         dv4l_env = getenv("DV4L_RGBONLY");
         vctx.vrgbonly = (dv4l_env != NULL);
+
+        dv4l_env = getenv("DV4L_WIDTH");
+        if(dv4l_env != NULL){
+            vid_resize_width = atoi(dv4l_env);
+        }
+
+        dv4l_env = getenv("DV4L_HEIGHT");
+        if(dv4l_env != NULL){
+            vid_resize_height = atoi(dv4l_env);
+        }
+
     }
 
     if(strcmp(name, "LD_PRELOAD") == 0) {
@@ -726,8 +739,16 @@ debug("#1 dv4l open libdv_init\n"); \
         vctx.vcap.maxheight = 0; \
 log("#2 dv4l open vfd %d fake_fd %d\n", vctx.vfd, fake_fd); \
         get_camsize(&vctx); \
-        vctx.vwin.width = vctx.vcap.maxwidth; \
-        vctx.vwin.height = vctx.vcap.maxheight; \
+        if(vid_resize_width > 0 && vid_resize_width < vctx.vcap.maxwidth) { \
+            vctx.vwin.width = vid_resize_width; \
+        } else { \
+            vctx.vwin.width = vctx.vcap.maxwidth; \
+        } \
+        if(vid_resize_height > 0 && vid_resize_height < vctx.vcap.maxheight) { \
+            vctx.vwin.height = vid_resize_height; \
+        } else { \
+            vctx.vwin.height = vctx.vcap.maxheight; \
+        } \
 debug("#3 dv4l open\n"); \
         iec61883_dv_set_buffers(iec61883_dv_fb_get_dv(vctx.viec), 1000); \
         if(iec61883_dv_fb_start(vctx.viec, 63) < 0) { \
diff -uNrp dv4l-1.0_org_cleaned/mkdv4lstart dv4l-1.0-ForceResizeAdded/mkdv4lstart
--- dv4l-1.0_org_cleaned/mkdv4lstart	2008-11-27 01:41:01.000000000 +0900
+++ dv4l-1.0-ForceResizeAdded/mkdv4lstart	2008-11-21 22:17:38.000000000 +0900
@@ -11,6 +11,12 @@ usage () {
     echo
     echo "Version " $2
     echo
+    echo "    -wd, --width"
+    echo "        Set width(pix) which you want to scale to."
+    echo
+    echo "    -ht, --height"
+    echo "        Set height(pix) which you want to scale to."
+    echo
     echo "    -c, --color-correction"
     echo "        Set this option if red objects look blue."
     echo
@@ -45,6 +51,16 @@ do
         DV4L_RGBONLY=1
         export DV4L_RGBONLY
         ;;
+    -wd | --width)
+        shift
+        DV4L_WIDTH=\$1
+        export DV4L_WIDTH
+        ;;
+    -ht | --height)
+        shift
+        DV4L_HEIGHT=\$1
+        export DV4L_HEIGHT
+        ;;
     -v | --verbose)
         shift
         DV4L_VERBOSE=\$1
diff -uNrp dv4l-1.0_org_cleaned/palettes.c dv4l-1.0-ForceResizeAdded/palettes.c
--- dv4l-1.0_org_cleaned/palettes.c	2008-11-27 01:41:01.000000000 +0900
+++ dv4l-1.0-ForceResizeAdded/palettes.c	2008-11-21 22:17:38.000000000 +0900
@@ -17,6 +17,7 @@
  * Author: Wolfgang Beck <bewo at users.berlios.de> 2007
  */
 
+#include <sys/time.h>
 #include <linux/videodev.h>
 
 /*

debian 温泉合宿へ


行ってきました、草津
初めての合宿経験。
とりあえず、自分は機材提供が最大の成果。。。(笑)
debianあまり関係ないけど、iphoneに入れたslコマンドを見せてみたり。じつはiphoneの勝手アプリのリポジトリはaptで管理されていて、パッケージもdebだったりする。

あとは先日のDNSポイゾニングの話について、どのような案内文がよいか、とかよい情報交換ができました。中にはなかなか不案内なリリース文もありましたよね。。。
さらにあの修正がすぐに破られた原因は、ソースポートのランダム性が充分ではないためだったということも聞けて参考になりました。

debianの課題の中では、web系開発者とかデザイナーとかも取り込んで、もっと受け入れられるLinuxにしたいよね、という話も出てきたり。他のweb系勉強会との共同開催とか提案してみましたが、詳細はまた考えてみます。

あと温泉気持ちよかった。特に雨に降られてびちょ濡れになった後は(笑)

反省点としてはDVカメラ持ってるんだから、持ってくればよかった。
debian勉強会怖くないよ、をテーマにストリーミングできるし。

(この文、写真のライセンスはGPLv2にします)

CUIからmixiエコーに投稿するruby書いてみた。

クラス使えてません。。。
あとxとかyって何かわかりません。
投稿結果も表示したいな。

#!/usr/bin/ruby
require 'rubygems'
require 'mechanize'
require 'kconv'

#setting
MAIL = 'foo@bar.com'
PASS = 'foobar'


#login
agent = WWW::Mechanize.new
loginpage = agent.get('http://mixi.jp/')
loginform = loginpage.forms.first
loginform['email'] = MAIL
loginform['password'] = PASS
loginform['next_url'] = '/home.pl'
agent.submit(loginform)

#pageget
mixiget = agent.get('http://mixi.jp/home.pl')
mixipost = agent.post('http://mixi.jp/add_echo.pl',
'body' => ARGV[0].toeuc,
'x' => '35',
'y' => '21',
'redirect' => 'home',
'post_key' => mixiget.forms[1].fields.name('post_key').value
)

iphone 3G 購入してきました。

じつは前回携帯買ったのは2ヶ月前。そのときはノキアの携帯買いました。
そして、最近グアム旅行で海に落としてしまい修理不能に。。。
まいったなー、と思いながらも、iphone購入する理由ができたのでiphone買っちゃいました。
とりあえず MacOSX につなげて普通に使えるようになったけど、今からLinuxで使えるように色々手を入れる予定。

写真は水没したノキア携帯のメモリカードに残ってたグアムの写真です。