2014年11月24日 星期一

使用git rebase 進行Pull Request 檢測

故事是這樣子的,自從我被加到Qucs project的專案小組,原本的管理員又因為博班進到最忙的時間開始比較少管事,變成我在管專案的Pull Request(PR,拉取要求)。
其實管就管,反正這個project 沒人鳥,平常也沒什麼PR進來;不過有PR進來的時候,還是要做適當的檢查,以下提一下幾個檢查PR的流程:

首先是把PR拉到本地資料夾,這部分參考github 的支援文件:
https://help.github.com/articles/checking-out-pull-requests-locally/

$ git fetch origin pull/ID/head:BRANCHNAME
其中ID 是github上Pull request 的編號,branchname則是你隨便取;這樣就會把網路上的提交全部拉下來,並創建一個新的分枝。

接著就要做點檢測工作,最重要的是所謂的阿蹦大神規則:每個提交都必須能編譯成功
這裡我會選用git rebase 搭配execute:
git rebase -i master –exec “make -C path”
git 就會在每個提交間插入一個執行make 的command,要注意git 在rebase 時的目錄位置是在專案最頂層的目錄(.git資料夾的所在),所以make 的路徑必須設好,不然rebae 會找不到make檔案,直接出錯;git會輸出下列的文件,在提交間插入執行命令,如果有提交不想要執行,可以手動移除掉。
pick 92c96a8 Add Xcode support to gitignore
exec make -C qucs/build
pick 0cdd379 Bugfix: LANGUAGEDIR
exec make -C qucs/build
pick 9a695b0 Skip Qt3 support for qucs-help
exec make -C qucs/build
execute的規則如下:
執行的指令如果回傳值非零,表示執行出錯,rebase 即會暫停在當前的提交,讓你有機會修正錯誤,可以使用git rev-parse HEAD來抓到當前的提交雜湊。

這樣就能放著電腦一直跑,反正出錯了就會停下來,表示這個Pull Request是有問題的,還不能合併到主線內。

2014年11月17日 星期一

使用autotool 編譯qt project

在寫這篇,我發現我曾經寫過類似的內容:
「使用gnu make編譯Qt 專案」
http://yodalee.blogspot.tw/2013/08/gnu-makeqt.html

總之,這次又是在qucs專案上遇到的問題,之前專案裡的使用者介面,不知道是哪根筋不對,竟然全部都是用手爆的啊啊啊!正好這個project現在進入巨量refactor階段,在改其中一個部分時,順手把其中一個使用者介面換用Qt的Designer來做。
結果,還要改編譯的autotool,讓它使用UIC解決才行,網路上找找沒什麼資料,只好印autotool的文件下來看,以下是我最後弄出來的Makefile.am設定:

首先,定義目標:此資料夾的靜態函式庫及它的原始碼:
noinst_LIBRARIES = libdialogs.a
libdialogs_a_SOURCES = xxxxx.cpp

原始碼後面可能有一大串,一般來說autotool有這樣有夠了,不過對Qt project,首先我們需要MOC: Meta object compiler將標題檔編譯為moc.cpp原始碼,並把它們加到靜態函式庫的原始碼中:
MOCHEADERS = xxxxx.h
MOCFILES = $(MOCHEADERS:.h=.moc.cpp)
.h.moc.cpp:
    $(MOC) -o $@ lt;
nodist_libdialogs_a_SOURCES = $(MOCFILES)
這會讓編譯器去編譯MOCFILES,找不到就用副檔名規則呼叫MOC產生.moc.cpp檔。

UIC比較麻煩一點,因為標頭檔不像原始碼一樣,會被加到編譯時的相依規則中,若像原始碼在相依規則裡,相依性不符時,Makefile會自動去尋找有沒有可以產生此相依的規則,如上面的.h.moc.cpp;但標頭檔就算全不見了,不設定的話Makefile也不會做什麼。
這裡我們使用autotool的BUILT_SOURCES來解,被加到這個變數的內容會優先被編譯:

UICFILES = ui_yyyy.h
BUILT_SOURCES = $(UICFILES)
ui_%.h: %.ui
    $(UIC) -o $@ lt;
noinst_HEADERS = $(MOCHEADERS) $(UIHEADERS)
如此一來就能順利的呼叫UIC幫我們產生標題檔了;不過含有%這種寫法是Makefile.am使用Gnu Extension達成的,因些在產生Makefile.in時會收到警告,如果不用這種寫法就只能寫明所有*.ui轉成ui_*.h的規則了。

rcc的話我是沒有用,不過它是將qrc檔轉成原始碼檔,因此估計使用方法跟moc比較像。

參考資料:
1. autotool BUILT_SOURCES:
http://www.gnu.org/savannah-checkouts/gnu/automake/manual/html_node/Sources.html

2014年11月4日 星期二

用python 產生未引入檔的報表

故事是這樣子的,最近我無聊的時候玩玩的專案
qucs 理想上這個project是要達成類似ADS的模擬軟體,很不幸的在功能上遠遠不足;project 開始的年代是2003年左右,當初應該是qt3 寫的,因為qt 支援不足那時候他們還自己寫qt 物件vtabbar, vtabbeddockwidget什麼的。
現在還有一堆qt3support的東西沒有清除乾淨,這整團又黏得死死得要一口氣清光,原作者去年有設法把它拿掉,弄了50多個提交之後沒什麼結果,物件繼承又弄得一團糟,十年累積下來的修改可以氣死人。

從一開始參與的時候,我就覺得這個project 很怪,怪在哪呢?無論是把Qt3拿掉升去Qt4,幫它加上新功能等等,不管你怎麼改,改了多少Qt的object進去,它編譯通常都會過,就算你沒include,它還是過了……
後來我才檢視到,在它最上層,位階僅次於main.h的qucs.h(光看名字就知道這有多核心XDD)裡,竟然加上了一行
#include <QtGui>
這…這行也真是霸氣外露,這根本是幫project加了個金剛不壞之身,之後所有的header files應該把qt object 的include都投去太平洋才是;這實在不是好習慣,QtGui 包含了太多的標頭定義,一方面會影響到編譯時間(下面連結有人說40%),同時也讓你抓不到每個檔案到底需要哪些標頭。

後來我用grep 下去掃一下,總共有兩個重要標頭檔被加上QtGui include,五個子資料夾d 重要標頭檔被加上這行,另外還有113個原始碼檔案。
如果要手動慢慢移除,大概會花上不少時間;也可以用sed 一個氣全移光,再來慢慢修,不管怎麼樣,移掉它們一定會產生一堆沒include標頭檔產生的 undefined error,要慢慢把該include 的檔案加上去。
老話一句:「working hard, after you know you are working smart」

為了這個問題,寫一個python code來解決。
問題很簡單:一但移除include QtGui,編譯時會產生一堆undefined error,我們要把該引入的標頭檔加上去,我的策略很簡單,用一個python script去剖析編譯的錯誤訊息,找去哪個檔案缺什麼定義,自動寫入暫存檔裡,產生該加上的include code即可。

呼叫make的地方就用python 的subprocess 模組,用make -k儘量產生各種錯誤:
from subprocess import Popen, PIPE
cmd = ['make', '-k', '-j8']
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, stderr = p.communicate()
errormsg = str(stderr)
這樣我們就取到錯誤訊息了,stdout其實可以不理它。

這個project用的是clang,它的message 形式是這樣:
檔名:行數:字元數: error: unknown type name '物件名'
該行的原始碼
可能的錯誤訊息有unknown type name跟implicit instantiation of undefined template。

要剖析也很簡單,連regular expression都不用,用split直接切下來就是了,最後我們用字典建立檔案跟缺少的物件列表:
files = {} filename = line.split(':')[0]
classname = line.split(“'”)[1]
if filename in files:
    if classname not in files[filename]:
        files[filename].append(classname)
    else:
        files[filename] = []
        files[filename].append(classname)
這樣我們有報表了,再來就是產生一下引入的原始碼,如果是標頭檔,會產生class forward declaration跟include引入,原始碼檔就比較簡單,全部產生引入即可,如此一來就能輕鬆產生引入的程式碼。最後我們還能用subprocess幫我們打開vim,把有錯的檔案跟我們寫入的報表一起打開,就能輕鬆修正所有的錯誤。

結果:
我花了大約30分鐘的時間,把120個檔案的QtGui 引入全部刪完了,單核心編譯時間從原本的344秒加速到245秒,真的大約提速29 % OAO

相關資料:
1. Qucs 專案:
https://github.com/Qucs/qucs
2. QtGui相關討論:
http://qt-project.org/forums/viewthread/18766