2014年8月30日 星期六

[books] 程序員的吶喊

這是本發牢騷的書, 比嘴炮更糟, 我是說真的, 從中得到的爽度遠大於知識, 前題是: 假如你和 Yegge 站在同一邊的話; 反過來的話你可能就覺得難受了。也只有程式員才會喜歡這樣的牢騷。20140818 購於台南若水堂 203 nt, 是 190 頁的小書。

原文本 Publication Date: December 1, 2012; 簡體中文版本 201405 出版。有些技術相關內容可能過時, 不過不打緊, 牢騷這種東西哪有什麼過時的問題。記住, 你看的是牢騷, 不是技術文章。

我一定是被折扣迷昏了腦袋才會去花錢買人家的牢騷, 特別是是看到書中提到的 lisp, 而這陣子我剛好被 lisp 洗腦, 對 lisp 相關的文章都有興趣, 衝動之下就買了。我還在 irc 推薦這本書, 聽了我建議的朋友, 知道買了一堆牢騷, 應該也會對我發一頓牢騷才是。

中文翻譯本果然充滿了濃厚的中國味 (那是當然, 中國人翻譯的簡體中文版本), 還沒能習慣 (應該永遠也無法習慣, 什麼時候才會有滿滿的台灣味可欣賞, 繁體中文的駭客與畫家就有濃濃的台灣味道, 我真喜歡, 可惜只有台灣味道令我喜歡), 但讀來也算通暢, 看來牢騷比技術文章好翻譯一點。

如果你有正常的品味, 可能不願意花錢買人家的牢騷吧! 來吧! 這裡有電子版本, 當然是英文的。還可以順便練習英文, 又加上免費, 哪裡有這樣的便宜事。

A Programmer's Rantings: On Programming-Language Religions, Code Philosophies, Google Work Culture, and Other Stuff

英文書名還蠻長的 (中文版也不遑多讓), 吸引目光的應該是那個和 google 沒什麼關係的 google 關鍵字。當然不是沒關係, 第三章有 4 篇文章和 google 有關。

這本書我原本不怎麼想提, 不過應聘 google (Get that job at Google) 這篇文章對於面試 google 提供了不少建議, 當然對其他公司的面試也是有幫助, 如果你不想看大部頭的面試書籍, 這篇可以應應急。

這篇提到幾本書:

  1. 算法設計手冊 (Steven Skiena's The Algorithm Design Manual), 他說算法導論不可能兩個禮拜看完, 知道這本書厚度的朋友應該都同意; 不過這本有 730 頁, 也不是能兩個禮拜看完的吧!

  2. java 並發編程 (Concurrent Programming in Java by Doug Lea)

  3. 算法導論 (Introduction to Algorithms)


我對第一本書有興趣, 資料結構和演算法的好書不嫌多。這篇文章大意就是資料結構課本上的東西你都要在面試 google 的時候要能回答出來, 對象是 google, 你應該不會有什麼好驚訝的吧!

其他文章呢? 我覺得沒看也不會怎麼樣, 而且多餘的句子很多, 就像是倪匡的小說一樣, 跳過幾百頁之後, 還是能和主線接上, 所以也不用特別介紹, 若你被編寫程式搞到發瘋, 可以看看這本書, 抒解一下心情。若你想練習怎麼把文章加上一堆冗長的句子, 也可向本書學習, 我不是在諷刺, 因為我就是打算這麼做。

不過我還是想提一下 Rich Programmer Food 這篇, 書上翻譯為《土豪程序員的美食》, 你一定丈二金剛, 完全不知道在說什麼吧! 裡頭的關鍵字是 - compiler, 在很多大學課程上, 算是屬於折磨人的一門課, 曾在網路上看到這句話, 修過作業系統的同學, 應該要有寫出一個小型作業系統的能力, 這句話你我都知道還有努力的空間, 所以 compiler 課程呢? 修過 compiler 的同學, 應該要有寫出一個簡易 compiler 的能力, 一樣還是有努力的空間, Yegge 也是第二次才修過這個課程, 所以讓我們一起努力。那學過計算機組織和架構的應該要做出一個小電腦了, 引申下去所有課程 ... 真是可怕。

我相信就像數學一樣, 大家都覺得 compiler 課程很重要, 但不會的話好像也能在就業市場上佔有一席之地, 不過你還是可以聽聽 Yegge 的意見 ... 是牢騷。

我自己倒不是因為 compiler 課程很重要, 可以幫助工作或是面試上一些大公司, 而是我本來就想「知道」這是什麼? 在我大學生涯上, 有 3 個人修這門課, 是的, 我是其中一位, 另外兩位我不能說, 加上老師, 正好可以打麻將, 一個都不能少。我的學業成績拿到了第三名的佳績, 你一定想問我拿到這麼優秀的成績, 那會寫 compiler 了嗎? 嗯 ... 這問題先不談, 這門學問真的不簡單, 尤其是工作後要自修真的不太容易, lex/yacc 也不是每個人隨時就能上手的工具。我有很多關於 compiler 的書籍, 但沒有一本是我完整翻過看完的。反倒是先完成了一個 os 小 kernel。

Yegge:「If you don't know how compilers work, then you don't know how computers work.」

我不知道這句話是不是正確的, 不過要知道電腦怎麼運作, 從寫一個小型 os kernel 也可以得知, 條條大路通羅馬, 只是這兩條路都不輕鬆就是, 唔 ... 也許還有第三條, 第四條 ... 但一定都不容易。

我喜歡他寫的一句話: 「學習是很痛苦的事情。」後面還有一句, 不過不寫出來了 (寫出來你會更痛苦)。要是你覺得學習很輕鬆, 你不是在摸魚就是天資異稟, 我相信是前者居多, 天才在真實世界可是稀有品種。

這本書我讀完了, 我不會像讀者評論的那樣轉去學習 ruby, 程式員可是相當頑固的, 你以為幾篇文章就能將我洗腦嗎? 你也是吧? 在我的編程生涯中, c/c++ 佔據了多數時間, 這傢伙把我的最愛 c++ 批了一大頓, 要我喜歡這傢伙怎麼可能, 但也不讓我討厭就是。

有朋友建議我學個 script language, 我也覺得我少了這塊拼圖, 最近 lisp 系的 scheme 補上了, 記得嗎? 我專門學老東西, 還順便補上了 lambda 和 functional programming, 這麼划算的語言不學嗎? compiler 我覺得太難, 退一步來學習 scheme 的 interpreter 怎麼實作, 它容易多了, 只要看 sicp 4.1.1 ~ 4.1.4 四個小節就可以, 不用看完一本大部頭。



當然實際上不會有那麼容易 (我已經花了幾個禮拜, 還沒什麼大進展), 但比起讀這些書, 真的簡化不少。

2014年8月23日 星期六

sicp metacircular evaluator (1) - read procedure

有些朋友說我的 blog 都是滿滿的組合語言, 那不是刻意為之的, 組合語言之前沒有秘密, 得把這樣的程式碼挖出來才能理解一些東西。

不過我這陣子換口味了, 滿滿的小括號應該會充斥版面一段時間。

sicp 4.1 要寫一個 Metacircular Evaluator, 簡單說就是用 lisp 來寫一個 lisp, 用到一個 read primitive procedure, 可以當成是內建的 procedure, 這是用來讀取鍵盤輸入的資料。我花了一陣子才搞懂。

r_t.scm
 1 (define (handle_read exp)
 2   (display (car exp))
 3   (newline)
 4   (display (cdr exp))
 5   (newline)
 6   )
 7 
 8 (define (aa)
 9   (newline)
10   (display "aaa")
11   (newline)
12   (handle_read (read))
13   (newline)
14   (display "bbb")
15   (newline)
16   'ok)
17 
18 (aa)

read 回傳的值就當成 handle_read 的 exp 參數內容。

執行結果:

result
 1 
 2 ad "r_t.scm")
 3 
 4 ;Loading "r_t.scm"...
 5 aaa
 6 ('a)
 7 (quote a)
 8 ()
 9 
10 bbb
11 ;... done
12 ;Value: ok
13 
14 1 ]=> (load "r_t.scm")
15 
16 ;Loading "r_t.scm"...
17 aaa
18 'z
19 quote
20 (z)
21 
22 bbb
23 ;... done
24 ;Value: ok
25 
26 1 ]=> 
27 
28 
29 (load "r_t.scm")
30 
31 ;Loading "r_t.scm"...
32 aaa
33 (+ 1 2 3)
34 +
35 (1 2 3)
36 
37 bbb
38 ;... done
39 ;Value: ok
40 
41 1 ]=> 

L6 輸入 ('a), read 回傳一個 list 包含 'a 和 ()。 
L18 輸入 'Z, read 回傳一個 list 包含 quote 和 Z。
L33, 輸入 (+ 1 2 3) read 會傳回一個 list 包含了 + 這個 symbol, 還有 1, 2, 3。


2014年8月17日 星期日

implement queue by mit-scheme

這是 sicp 3.3.2 節的程式碼

queue.scm
 1 (define (front-ptr queue) (car queue))
 2 (define (rear-ptr queue) (cdr queue))
 3 (define (set-front-ptr! queue item) (set-car! queue item))
 4 (define (set-rear-ptr! queue item) (set-cdr! queue item))
 5 (define (empty-queue? queue) (null? (front-ptr queue)))
 6 (define (make-queue) (cons '() '()))
 7 (define (front-queue queue)
 8   (if (empty-queue? queue)
 9     (error "FRONT called with an empty queue" queue)
10     (car (front-ptr queue))))
11 
12 (define (insert-queue! queue item)
13   (let ((new-pair (cons item '())))
14     (cond ((empty-queue? queue)
15            (set-front-ptr! queue new-pair)
16            (set-rear-ptr! queue new-pair)
17            queue)
18           (else
19             (set-cdr! (rear-ptr queue) new-pair)
20             (set-rear-ptr! queue new-pair)
21             queue))))
22             
23 (define (delete-queue! queue)
24   (cond ((empty-queue? queue)
25          (error "DELETE! called with an empty queue" queue))
26         (else
27           (set-front-ptr! queue (cdr (front-ptr queue)))
28           queue)))
29 
30 (define q1 (make-queue))
31 (insert-queue! q1 5)
32 (insert-queue! q1 6)
33 (insert-queue! q1 7)

我被 cons 的 pair 搞亂, 花了一天才看懂這程式碼。而且很難得的畫了一些圖來說明 (畫這些圖形還真是難倒我, 醜了些, 大家將就看些)。



這是所使用的資料結構, cons 會建立一個 pair, 像上圖的長方形, 有兩個點, 指向另外的 pair。

(define q1 (make-queue))




(insert-queue! q1 5)




(insert-queue! q1 6)

寄件者 sicp-queue



(insert-queue! q1 7)



經過每個圖片與程式碼的對照, 應該可以理解為什麼程式會寫成這個樣子。

L23 的 delete-queue 相對也就很好理解了。

使用這樣的寫法是因為效率的因素, 不需要一次又一次的呼叫 cdr 來取最後的元素, 就算使用 scheme 還是可以兼顧效率的。

若在面試上考 queue 並且不限制語言的話, 用 lisp 系寫出來, 面試官會對你印象深刻吧!


2014年8月12日 星期二

因為返回值而造成執行的錯誤訊息 (c++)

git url https://github.com/descent/scheme_eval
git branch add
git commit: 254282e3336ed62010d0a62500aeb2d60665fd04

這篇要紀錄一個很奇怪的執行錯誤:

error message
 1 descent@debian-vm:scheme_eval$ ./s_eval 
 2 90> (+ 1 2)
 3 
 4 (+ , 1 , 2 , ) 
 5 in list
 6 in exp:1
 7 in exp:2
 8 in symbol:+
 9 apply:Symbol
10 
11 + , 
12 (1 , (2 , () ) ) 
13 num: 1
14 num: 2
15 sum: 3
16 *** Error in `./s_eval': free(): invalid pointer: 0xb75844a4 ***
17 ======= Backtrace: =========
18 /lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x75e72)[0xb744de72]
19 /lib/i386-linux-gnu/i686/cmov/libc.so.6(+0x76bb0)[0xb744ebb0]
20 /usr/lib/i386-linux-gnu/libstdc++.so.6(_ZdlPv+0x1f)[0xb763099f]
21 /usr/lib/i386-linux-gnu/libstdc++.so.6(_ZNSs4_Rep10_M_destroyERKSaIcE+0x1b)[0xb7694cfb]
22 /usr/lib/i386-linux-gnu/libstdc++.so.6(+0x4665a)[0xb762e65a]
23 /usr/lib/i386-linux-gnu/libstdc++.so.6(_ZNSsD1Ev+0x2e)[0xb7694d5e]
24 ./s_eval[0x804c4b1]
25 ./s_eval[0x804ef5a]
26 ./s_eval[0x804e4f0]
27 ./s_eval[0x804dbfb]
28 ./s_eval[0x804d0e2]
29 ./s_eval[0x804c5c5]
30 ./s_eval[0x804c4a3]
31 ./s_eval[0x804b69e]
32 ./s_eval[0x804b8ef]
33 ./s_eval[0x804bc9d]
34 ./s_eval[0x804bd57]
35 /lib/i386-linux-gnu/i686/cmov/libc.so.6(__libc_start_main+0xf5)[0xb73f18f5]
36 ./s_eval[0x80493c1]
37 ======= Memory map: ========
38 08048000-08053000 r-xp 00000000 08:11 299681     /home/descent/git/scheme_eval/s_eval
39 08053000-08054000 rw-p 0000b000 08:11 299681     /home/descent/git/scheme_eval/s_eval
40 0940d000-0942e000 rw-p 00000000 00:00 0          [heap]
41 b73d6000-b73d8000 rw-p 00000000 00:00 0 
42 b73d8000-b7581000 r-xp 00000000 08:01 659097     /lib/i386-linux-gnu/i686/cmov/libc-2.17.so
43 b7581000-b7582000 ---p 001a9000 08:01 659097     /lib/i386-linux-gnu/i686/cmov/libc-2.17.so
44 b7582000-b7584000 r--p 001a9000 08:01 659097     /lib/i386-linux-gnu/i686/cmov/libc-2.17.so
45 b7584000-b7585000 rw-p 001ab000 08:01 659097     /lib/i386-linux-gnu/i686/cmov/libc-2.17.so
46 b7585000-b7588000 rw-p 00000000 00:00 0 
47 b7588000-b75a3000 r-xp 00000000 08:01 659209     /lib/i386-linux-gnu/libgcc_s.so.1
48 b75a3000-b75a4000 rw-p 0001a000 08:01 659209     /lib/i386-linux-gnu/libgcc_s.so.1
49 b75a4000-b75a5000 rw-p 00000000 00:00 0 
50 b75a5000-b75e6000 r-xp 00000000 08:01 659084     /lib/i386-linux-gnu/i686/cmov/libm-2.17.so
51 b75e6000-b75e7000 r--p 00040000 08:01 659084     /lib/i386-linux-gnu/i686/cmov/libm-2.17.so
52 b75e7000-b75e8000 rw-p 00041000 08:01 659084     /lib/i386-linux-gnu/i686/cmov/libm-2.17.so
53 b75e8000-b76c4000 r-xp 00000000 08:01 558350     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.19
54 b76c4000-b76c5000 ---p 000dc000 08:01 558350     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.19
55 b76c5000-b76c9000 r--p 000dc000 08:01 558350     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.19
56 b76c9000-b76ca000 rw-p 000e0000 08:01 558350     /usr/lib/i386-linux-gnu/libstdc++.so.6.0.19
57 b76ca000-b76d1000 rw-p 00000000 00:00 0 
58 b76e7000-b76ec000 rw-p 00000000 00:00 0 
59 b76ec000-b76ed000 r-xp 00000000 00:00 0          [vdso]
60 b76ed000-b770c000 r-xp 00000000 08:01 659073     /lib/i386-linux-gnu/ld-2.17.so
61 b770c000-b770d000 r--p 0001f000 08:01 659073     /lib/i386-linux-gnu/ld-2.17.so
62 b770d000-b770e000 rw-p 00020000 08:01 659073     /lib/i386-linux-gnu/ld-2.17.so
63 bfc50000-bfc71000 rw-p 00000000 00:00 0          [stack]
64 Aborted

程式在執行後吐出 L16 之後的錯誤訊息, 真是令人害怕。出問題的程式碼在下面:

s_eval.cpp
156 cell proc_add(const cell &c)
157 {
158   int sum = add_cell(c);
159   cout << "sum: " << sum << endl;
160   cell(Number, str(sum)); // should return cell
161 }


396 cell apply(const cell &func, const cell &args)
397 {
398   cout << "apply:" << func.kind_str() << endl;
399   cout << endl;
400   print_cell(func);
401   cout << endl;
402   print_cell(args);
403   cout << endl;
404 
405   if (func.kind() != Proc)
406   {
407     // func.proc(args);
408   }
409   proc_add(args);
410  
411 
412   return func;
413 }

L160 沒有正確 return cell 就引發這錯誤, 實在令我驚訝, L409 呼叫 proc_add 後, 我也沒有用到 return 的 cell, 就算我沒在 proc_add return cell, 應該也不會有錯阿, 我是這麼想的。

一定有程式碼執行了什麼才引發這錯誤, 雖然我沒使用回傳的 cell object, 但是 c++ compiler 幫我們安插了 destructor, 應該是在執行這個 destructor 時發生錯誤的。

c++ code
1 sh-4.2$ c++filt _ZNSsD1Ev
2 std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()

有發動 std::string destructor 但是該位址上的東西不是可以正常執行的, 因為我沒 return cell, 那個空間是未知程式碼, 所以就造成這可怕的結果。

我沒有反組譯來證明, 這是冥想出來的。歡迎有其他想法的朋友一起討論。

2014年8月8日 星期五

安裝 xv6

我從這本書得知 unix v6, 10000 行的 source code 是有可能看完的, 可是執行的平台卻是 pdp-11, 是不是要為了 unix v6 而去研究 pdp-11 這平台的 source code 實在令人猶豫, 我個人認為是划不來, 要花上的心力可想而知。

我雖然喜歡研究老東西, 不過也是有選擇的, 通常都是在這個時代還可以運用的技術。對於 cpu 我不想深入研究這已經退出舞台的機器。

需要做什麼準備工作呢? 我得去搞定 pdp 模擬器 simh (我可不想去弄台 pdp 機器, 家裡可沒大到擺這台), 可不是執行起來就夠, 還得能讓我 single step source code 才行。再來還有 pdp 的硬體架構, mmu, pre K&R c 語法, 研究 pdp-11 組合語言, 搞定 pdp-11 組譯器、老舊的 c 編譯器, 長長附錄 A 裡頭的參考資料可不是這麼容易就可以搞定的, 還沒開始看 code 就去半條命了。學到的 pdp 硬體架構也許有所幫助, 但以 cp 值來說實在是太低了, 這些東西都不好學, 要運用在目前流行的 x86/arm 還是要重頭學習一次, 能在目前流行的 x86/arm 學一次那就太好了。

xv6 就這麼出現了, 這是 mit 老師們為了教學和解除我上述的問題而在 x86 上開發的移植版本, 也大概是 10000 行左右 (組合語言和 c 語言), 我有了之前的學習, 應該可以看懂這 source code, 這比學習 linux 大怪物成功的機會大了不少, 和 linux 0.11 很類似。

安裝 xv6 到模擬器上

get source code
git clone git://pdos.csail.mit.edu/xv6/xv6.git
打個 make 很順利的產生需要的檔案。

在 qemu 執行 xv6
修改 Makefile

QEMU = qemu-system-i386
make qemu 

實際上執行的指令是:
qemu-system-i386 -serial mon:stdio -hdb fs.img xv6.img -smp 2 -m 512
執行訊息:

xv6...
cpu1: starting
cpu0: starting
init: starting sh
乖乖, 還真的支援多 cpu, 真的厲害, 當然, 拿掉 smp 就沒有 cpu1 了。

bochs 的話, 我沒有使用這個  dot-bochsrc, 我新增了一個可以在 bochs 2.6.6 使用的設定檔 bochsrc-266-conf。

/usr/local/bin/bochs -f  bochsrc-266-conf

我真羨慕能上這個課程的學生, 我只能自己辛苦打拚了。

我 clone 一份, 放在 github
git@github.com:descent/xv6.git

該從什麼地方開始研究呢? booting 好了, 從 Makefile 就可以看出端倪。

xv6.img: bootblock kernel fs.img
        dd if=/dev/zero of=xv6.img count=10000
        dd if=bootblock of=xv6.img conv=notrunc
        dd if=kernel of=xv6.img seek=1 conv=notrunc

由 qemu/bochs 指令可以得知需要 xv6.img, fs.img, 而 xv6.img 就是這樣產生的, 需要 bootblock。

bootblock: bootasm.S bootmain.c
        $(CC) $(CFLAGS) -fno-pic -O -nostdinc -I. -c bootmain.c
        $(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S
        $(LD) $(LDFLAGS) -N -e start -Ttext 0x7C00 -o bootblock.o bootasm.o bootmain.o
        $(OBJDUMP) -S bootblock.o > bootblock.asm
        $(OBJCOPY) -S -O binary -j .text bootblock.o bootblock
        ./sign.pl bootblock

看到熟悉的 0x7c00, legacy bios booting 方式, 那 bootasm.S bootmain.c 就是開機的精華所在了。

sign.pl
 1 #!/usr/bin/perl
 2 
 3 open(SIG, $ARGV[0]) || die "open $ARGV[0]: $!";
 4 
 5 $n = sysread(SIG, $buf, 1000);
 6 
 7 if($n > 510){
 8   print STDERR "boot block too large: $n bytes (max 510)\n";
 9   exit 1;
10 }
11 
12 print STDERR "boot block is $n bytes (max 510)\n";
13 
14 $buf .= "\0" x (510-$n);
15 $buf .= "\x55\xAA";
16 
17 open(SIG, ">$ARGV[0]") || die "open >$ARGV[0]: $!";
18 print SIG $buf;
19 close SIG;

我看不懂 perl, 似乎只是加上 0x55aa。

bootmain.c
void readsect(void *dst, uint offset)
{
  // Issue command.
  waitdisk();
  outb(0x1F2, 1);   // count = 1
  outb(0x1F3, offset);
  outb(0x1F4, offset >> 8);
  outb(0x1F5, offset >> 16);
  outb(0x1F6, (offset >> 24) | 0xE0);
  outb(0x1F7, 0x20);  // cmd 0x20 - read sectors

  // Read data.
  waitdisk();
  insl(0x1F0, dst, SECTSIZE/4);
}

這是用 LBA mode 讀寫硬碟的程式碼, x86汇编语言:从实模式到保护模式 8.3.5 有詳細的說明, 這樣就不用 bios call, 可在 x86 protected mode 存取硬碟。不過不知道在真實機器上是不是可以正常執行?

ref:

readsect() 硬碟讀寫方式不使用 bios call:

2014年8月1日 星期五

qt 5.3 for android - 編譯流程 (指令篇)

env:
linux debian unstable 32bit

  1. adt-bundle-linux-x86-20140702.zip
  2. android-ndk32-r10-linux-x86.tar.bz2
  3. qt-creator-opensource-src-3.1.2.tar.gz
  4. qt-opensource-linux-x86-android-5.3.1.run
  5. qt-everywhere-opensource-src-5.3.1.tar.gz
總共需要 1, 2, 3 (本篇不需要這個), 4, 第五項是 qt 5.3.1 source code, 如果想要自己編譯 qt for android, 才會需要這個, 不過 qt 預先編譯好的應該就夠用了。

第三項是因為 qt-opensource-linux-x86-android-5.3.1.run 內附的 qt-creator 我每次都無法正常開啟, 所以我需要從 source code 編譯。

設定 qtcreator
tool/options/Build & Run/Qt Versions add /home/descent/Qt5.3.1/5.3/android_armv7/bin/qmake。
tool/options/android 設定 sdk, ndk

不過還好, qtcreator 已經用不到了,以下是全指令的編譯方式, 我已經受夠了按按紐只得到錯誤訊息, 根本編不出任何東西來。

以 /media/winxp/android-dev/qt-everywhere-opensource-src-5.3.1/qtbase/examples/widgets/itemviews/dirview/ 範例來介紹編譯流程:

export ANDROID_NDK_ROOT=/home/descent/and-dev/android-ndk-r10/
/home/descent/Qt5.3.1/5.3/android_armv7/bin/qmake dirview.pro 

descent@debianlinux:dirview$ ls
android-libdirview.so-deployment-settings.json  dirview.pro  main.cpp  Makefile
descent@debianlinux:dirview$ 

make # 編譯出 libdirview.so, 一般的 qt 程式到這裡就結束了, android app 還有其他步驟要做。
make install INSTALL_ROOT=./android-build # 初使化 android build 目錄

/home/descent/and-dev/adt-bundle-linux-x86-20140702/sdk/tools/android
選擇 Tools/Manage AVDs, 先把模擬器執行起來。

/home/descent/Qt5.3.1/5.3/android_armv7/bin/androiddeployqt --input android-libdirview.so-deployment-settings.json  --output ./android-build --deployment bundled --install --ant /usr/bin/ant --android-platform android-20 --jdk /usr/lib/jvm/java-7-openjdk-i386 --device emulator-5554 # 先建立 build.xml 讓 ant 使用, 再去 call ant 建立 apk, 並把 apk 傳到模擬器中

descent@debianlinux:platform-tools$ ./adb devices
List of devices attached 
emulator-5554 device 

到這裡應該可以在模擬器執行這個 app 了。

也可以從 /home/descent/and-dev/dirview/android-build 下 ant 指令
ant clean release
編譯出 *.apk

/home/descent/and-dev/dirview/android-build/android-build/bin/*.apk

我有遇到以下紅色部份的錯誤訊息, 我不知道如何設定正確的路徑, 只好建立一個 symbolic link 到該路徑, 就沒有問題了, 也不用在啟動那個很大很慢的 qt creator 了。
Android build platform: android-20
Install to device: emulator-5554
Cannot find Android tool: /opt/android/sdk/tools/android

--android-platform android-17 android 4.2.2
--android-platform android-20 android 4.4w

我另外安裝了 api-17 for android 4.2.2。

你也許會好奇我怎麼知道這些指令的, 噢! 那個折磨我的 qt creator 裡頭有 log 可以看, 從裡頭挖出這些指令。

androiddeployqt 指令: http://qt-project.org/doc/qt-5/deployment-android.html

在手機上 (歌林 7 吋雙核雙卡手機平板) 的執行畫面 (3 apps):



檔案大小和所安裝需要的權限:


這是以 NDK 方式執行的, 應該有效率的優勢, 但是最近火紅的 zenfone 可能無法執行, 得要重編譯一個 x86 版本才行。

從 5.1, 5.2 到 5.3 總算讓我搞定開發環境了。

debian 64bit

env:
debian 64bit

for run /home/descent/android-dev/adt-bundle-linux-x86_64-20140624/sdk/platform-tools/adb
adb 還沒有 64bit 版本, 需要安裝相對應的 i386 套件。

sudo dpkg --add-architecture i386
apt-get update
sudo apt-get install libc6:i386 libstdc++6:i386 libgcc1:i386 zlib1g:i386 libncurses5:i386

for android emulator
sudo apt-get install libsdl1.2debian:i386

ref: http://stackoverflow.com/questions/2710499/android-sdk-on-a-64-bit-linux-machine

若用舊版 qtcreator 可能遇到這問題 (我覺得直接用最新版的 source code 編譯解決這問題比較好):
Qt5 for Android: incompatible ABI
http://stackoverflow.com/questions/22238649/qt5-for-android-incompatible-abi
QtCreator -> Projects -> BuildEnvironment add the variable
ANDROID_TARGET_ARCH = default/armeabi-v7a

/home/descent/Qt5.3.1/5.3/android_armv7/bin/androiddeployqt --input android-liba1.so-deployment-settings.json  --output ./android-build --deployment bundled --install --ant /usr/bin/ant --android-platform android-17 --jdk /usr/lib/jvm/java-7-openjdk-amd64/ --device emulator-5554 --verbose

我很想測試一下 iOS 版本, 我也有 mac, 不過版本太舊, 無法執行 iOS toolchain, 所以等待未來購入新 mac 機器再來測試。

同樣的 source code, 可以產生 linux/X, windows, mac, android 這些平台的應用程式, 實在令人興奮, 這樣的投資很划算阿!

android 2.3.3 api 10

根據這份文件 Qt5.3 For Andoid 安装过程, 需要安裝Android armv5。



會多出一個 Qt5.3.1/5.3/android_armv5/, 不過我使用這目錄的 qmake, androiddeployqt, 還是無法成功編譯出 android 2.3.3 的 app。

~/Qt5.3.1/5.3/android_armv5/bin/qmake dirview.pro

/home/descent/Qt5.3.1/5.3/android_armv5/bin/androiddeployqt --input android-libdirview.so-deployment-settings.json   --output ./android-build --deployment bundled --install --ant /usr/bin/ant --android-platform android-10 --jdk /usr/lib/jvm/java-7-openjdk-i386 --verbose

--verbose 在有錯誤時可以印出多點訊息。

不管他了。

我似乎搞錯了,  查看 android-build/AndroidManifest.xml
41 <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="14"/>
即可確認這個 android app 可以在哪個版本執行, 我在模擬器和真實機器 (twm amazing a1 android 2.3.3) 測試過, 可以正常執行。所以不需要特別下
--android-platform android-10 用 --android-platform android-20 就可以了。

ref: