转载:
一个autobuild系统, 如果您和我有同样的需求, 不妨跟随我一起搭建一个这样的系统, easy and simple, have fun :)
Why:
事情是这样开始的, 我自己的项目放置在github上, 这是一个linux项目, 然而我所用来开发的机器是mac, 它不能用来编译所有的模块, 每次我都需要push提交之后, 再去server上pull下来, 然后编译看看结果, 这个过程我持续了一阵子之后再也忍受不了了, 我想要一个这样的系统:
1. 当我push提交之后, 他能实时的反馈给我编译的结果, 就像本地编译一样,
2. 运行test unit, 并将结果实时反馈给我
3. 随后发送一封邮件到我的邮箱内, 包含编译结果以及test unit运行结果
4. 而这次的提交仅仅是为了测试集成的效果, 而不影响真正的版本库主干
How:
目的明确后, 我想到了git hook, 这里面的hook有一个可以满足我的需求-- post-receive, 这是一个server端的hook(shell脚本), 每当接收到applypatch后都会调用一次并将输出结果反馈给client, 接下来我们将开始利用这个hook搭建这套系统.
首先我们来看一下工作流程(如下图):
图1 WorkFlow
简单的描述一下这个流程, 相对还算清晰简洁, 找一台可以用作build的机器, 建立一个git bare repo 用于接受提交, a clone repo for update & build, 当你向这个repo push后, server收到commit会自动调用post-receive hook, 接下来你需要让这个hook脚本做4件事:
1. 定位到clone repo 目录中进行pull, 更新最后一次提交
2. 在build目录中执行make
3. 执行TestUnit
4. 将刚才输出的信息发送到指定邮箱
这个脚本完成上述4个步骤, 也就完成了我们制定的目标. 现在所有重点都在这个hook脚本内, 不过你是否有一个疑惑, 这些执行中的信息如何才能反馈给提交的client? 这个也是我最先想问的, 查了git hook的资料后, 发现post-receive执行时, 标准输出(stdout)和标准错误输出(stderr)都会被git-send-back给当前client, 所以这个传输问题我们可以不用担心, 我们只需要关心哪些信息我们需要收集, 哪些需要丢弃即可.
ToDo:
目标清楚了, 原理也清楚了, 接下来我会展示如何搭建一个git server ,以及如何编写这个git hook(post-receive), 到最后测试结果演示.
1. Git server:
搭建server很多文章中都有描述, 我稍微提炼一下, 做一个最精简的. 首先我们找一台机器(要有root or sudo权限)
1). mkdir -p git_repo/autobuild ( git_repo为git server的根目录, autobuild为旗下的其中一个repo )
2). cd git_repo && git --bare init
3). sudo git daemon --verbose --export-all --base-path=gir_repo --enable=upload-pack --enable=upload-archive --enable=receive-pack (这个可以做成一个shell 也可以把git daemon注册成service, 这个就不再这里过多讨论了 各有喜好)
完成上述3步后, 一个简单的git server就搭好了, 现在我们需要将其clone出来到一个目录中用于接下来做autobuild环境. (今后每次receive commit后我们都会到这个目录做一次git pull更新最后一次提交, 并进行后续操作)
比如 我们将其clone到一个名为"/test_env"的目录下:
git clone git://127.0.0.1/autobuild test_env
好了到此, 搭建git server并准备autobuild环境目录已经完成, 接下来我们开始编写post-receive hook了.
2. Post-receive Hook:
刚刚也提到了, 这个脚本就是我们今天的主角, 那么我们先回到git_repo/autobuild目录来, 在这里面, 我们可以看到hooks目录, 进去之后我们可以找到post-receive.sample 这个模板, 将其rename为post-receive, 并添加我们需要的工作流程:
一个简洁的例子, 内容如下:
- mailfile='autobuild.txt'
- email='xxx@xx.com'
- unset GIT_DIR
- # 1. git pull
- cd /git_repo/autobuild
- git pull > $mailfile
- # 2. build
- make clean
- make --no-print-directory 2>> $mailfile
- # 3. run test unit
- make run_test --no-print-directory >> $mailfile
- # 4. output to stdout for echo back
- cat $mailfile
- # 5. send a email to yourself
- mail -s "Autobuild `date`" $email < $mailfile
mailfile='autobuild.txt'
email='xxx@xx.com'
unset GIT_DIR
# 1. git pull
cd /git_repo/autobuild
git pull > $mailfile
# 2. build
make clean
make --no-print-directory 2>> $mailfile
# 3. run test unit
make run_test --no-print-directory >> $mailfile
# 4. output to stdout for echo back
cat $mailfile
# 5. send a email to yourself
mail -s "Autobuild `date`" $email < $mailfile
上面的例子仅仅完成了我们工作流程中的目标, 并没有进行任何排版工作, 为了使邮件的格式更加清晰, 你可以在其中放置更多的echo, 比如打上一些横线作为分割, 写上一些提示语句, 这个就看大家自己的喜好了, 我就不在这里赘赘的描述了 :), 接下来, 你需要将这个repo加入到你本地的git remote列表中(使用remote add命令添加), 每当你想要autobuild的时候, 就将当前branch push到这个remote repo中, 你会在push执行时得到上述我们提到的所有反馈(build info, run test unit, receive a email for backup). That's All.
最后, 附上一个我的邮件信息格式, 以供参考:
- 2011年 11月 27日 星期日 23:04:46 EST
- hi, This is a Autobuild message, please don't reply!
- ------------------------Update Info-------------------------------
- Merge made by recursive.
- ftu/Makefile | 2 +-
- 1 files changed, 1 insertions(+), 1 deletions(-)
- ------------------------Build Result------------------------------
- No errors
- ------------------------Test Result-----------------------------------
- ( test -d test && cd test && make run_test )
- (cd ../final_libraries/bin && ./test )
- FINAL TEST UNIT START...
- <<<<<<< CASE NAME:test_list DESCRIBE:for test flist >>>>>>>
- [ALL ASSERT PASSED -- 11/11]
- <<<<<<< CASE NAME:test_hash DESCRIBE:for test fhash set and get >>>>>>>
- 0--2717/2775
- 1--3238/4163
- 2--2769/2775
- 3--3272/4163
- 4--2742/2775
- 5--3284/4163
- 6--2751/2775
- 7--3260/4163
- 8--2741/2775
- 9--3226/4163
- total count = 30000 max_size = 34690(cost 589730 byte) use ratio=1.00 avg len=3000
- hash iter totoal=30000
- hashforeach totoal=30000
- [ALL ASSERT PASSED -- 30003/30003]
- <<<<<<< CASE NAME:test_hash_del DESCRIBE:for test hash_del method >>>>>>>
- hash del complete
- [ALL ASSERT PASSED -- 20000/20000]
- <<<<<<< CASE NAME:test_mem DESCRIBE:for test mempool alloc and free >>>>>>>
- main tid=47601252718336
- tid=47601254823680
- tid=47601256924928
- mempool init complete tid=47601254823680
- tid=47601254823680 size=4 diff_time:155540usec | 155ms
- tid=47601256924928 size=4 diff_time:183863usec | 183ms
- tid=47601254823680 size=12 diff_time:153841usec | 153ms
- tid=47601256924928 size=12 diff_time:146459usec | 146ms
- tid=47601256924928 size=20 diff_time:146551usec | 146ms
- tid=47601254823680 size=20 diff_time:170322usec | 170ms
- tid=47601256924928 size=30 diff_time:144495usec | 144ms
- tid=47601254823680 size=30 diff_time:152498usec | 152ms
- tid=47601256924928 size=36 diff_time:148525usec | 148ms
- tid=47601254823680 size=36 diff_time:152242usec | 152ms
- tid=47601256924928 size=60 diff_time:136363usec | 136ms
- tid=47601254823680 size=60 diff_time:136486usec | 136ms
- tid=47601256924928 size=100 diff_time:139143usec | 139ms
- tid=47601254823680 size=100 diff_time:148458usec | 148ms
- tid=47601256924928 size=180 diff_time:155570usec | 155ms
- tid=47601254823680 size=180 diff_time:137590usec | 137ms
- tid=47601254823680 size=300 diff_time:147943usec | 147ms
- tid=47601256924928 size=300 diff_time:154894usec | 154ms
- tid=47601254823680 size=600 diff_time:147344usec | 147ms
- tid=47601256924928 size=600 diff_time:155993usec | 155ms
- tid=47601254823680 size=1000 diff_time:149958usec | 149ms
- tid=47601256924928 size=1000 diff_time:146590usec | 146ms
- tid=47601254823680 size=1200 diff_time:140344usec | 140ms
- tid=47601256924928 size=1200 diff_time:157028usec | 157ms
- tid=47601254823680 size=3000 diff_time:154509usec | 154ms
- tid=47601256924928 size=3000 diff_time:159357usec | 159ms
- tid=47601254823680 size=5000 diff_time:148422usec | 148ms
- tid=47601256924928 size=5000 diff_time:147129usec | 147ms
- tid=47601254823680 size=8202 diff_time:151623usec | 151ms
- tid=47601256924928 size=8202 diff_time:153929usec | 153ms
- tid=47601254823680 size=16394 diff_time:146452usec | 146ms
- tid=47601256924928 size=16394 diff_time:141301usec | 141ms
- tid=47601254823680 size=51200 diff_time:257559usec | 257ms
- tid=47601254823680 total diff_time:2651372usec | 2651ms avg=155ms
- tid=47601256924928 size=51200 diff_time:243754usec | 243ms
- tid=47601256924928 total diff_time:2661121usec | 2661ms avg=156ms
- [ALL ASSERT PASSED -- 52094510/52094510]
- <<<<<<< CASE NAME:test_realloc DESCRIBE:for test mempool realloc >>>>>>>
- [ALL ASSERT PASSED -- 8/8]
- <<<<<<< CASE NAME:test_log DESCRIBE:for test log system >>>>>>>
- log work thread start
- read log info:[2011-11-27 23:04:55][debug]:log test final
- find ptr=0x7fff5036ce2d
- [ALL ASSERT PASSED -- 3/3]
- <<<<<<< CASE NAME:test_mbuf DESCRIBE:for test mbuf of mbuf_seek & rewind & realloc >>>>>>>
- [ALL ASSERT PASSED -- 31/31]
- <<<<<<< CASE NAME:test_mbuf1 DESCRIBE:for test mbuf of mbuf_push & mbuf_pop >>>>>>>
- [ALL ASSERT PASSED -- 90/90]
- <<<<<<< CASE NAME:test_timer DESCRIBE:for test ftimerfd >>>>>>>
- [ALL ASSERT PASSED -- 4/4]
- <<<<<<< CASE NAME:test_fev DESCRIBE:for test fev for create register add del methods >>>>>>>
- [ALL ASSERT PASSED -- 18/18]
- <<<<<<< CASE NAME:test_fev_listener DESCRIBE:for test fev listener >>>>>>>
- test listener thread startup
- wait for poll
- net_conn:connect sucess fd = 6
- [ALL ASSERT PASSED -- 5/5]
- <<<<<<< CASE NAME:test_fev_buff DESCRIBE:for test fev buff >>>>>>>
- wait for poll
- net_conn:connect sucess fd = 6
- read size=12, read_str=hello final
- main recv str=hi final
- evbuff error
- error happened haha
- [ALL ASSERT PASSED -- 22/22]
- <<<<<<< CASE NAME:test_fev_conn DESCRIBE:for test fev asynchronous connect >>>>>>>
- main tid=47601252718336
- wait for poll
- accept sucessful
- tid=47601256924928
- [ALL ASSERT PASSED -- 2/2]
- --------------------------------------
- TOTAL CASE 13, PASS 13, FAILED 0
- Best Regards! Final Autobuild Team
2011年 11月 27日 星期日 23:04:46 EST
hi, This is a Autobuild message, please don't reply!
------------------------Update Info-------------------------------
Merge made by recursive.
ftu/Makefile | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
------------------------Build Result------------------------------
No errors
------------------------Test Result-----------------------------------
( test -d test && cd test && make run_test )
(cd ../final_libraries/bin && ./test )
FINAL TEST UNIT START...
<<<<<<< CASE NAME:test_list DESCRIBE:for test flist >>>>>>>
[ALL ASSERT PASSED -- 11/11]
<<<<<<< CASE NAME:test_hash DESCRIBE:for test fhash set and get >>>>>>>
0--2717/2775
1--3238/4163
2--2769/2775
3--3272/4163
4--2742/2775
5--3284/4163
6--2751/2775
7--3260/4163
8--2741/2775
9--3226/4163
total count = 30000 max_size = 34690(cost 589730 byte) use ratio=1.00 avg len=3000
hash iter totoal=30000
hashforeach totoal=30000
[ALL ASSERT PASSED -- 30003/30003]
<<<<<<< CASE NAME:test_hash_del DESCRIBE:for test hash_del method >>>>>>>
hash del complete
[ALL ASSERT PASSED -- 20000/20000]
<<<<<<< CASE NAME:test_mem DESCRIBE:for test mempool alloc and free >>>>>>>
main tid=47601252718336
tid=47601254823680
tid=47601256924928
mempool init complete tid=47601254823680
tid=47601254823680 size=4 diff_time:155540usec | 155ms
tid=47601256924928 size=4 diff_time:183863usec | 183ms
tid=47601254823680 size=12 diff_time:153841usec | 153ms
tid=47601256924928 size=12 diff_time:146459usec | 146ms
tid=47601256924928 size=20 diff_time:146551usec | 146ms
tid=47601254823680 size=20 diff_time:170322usec | 170ms
tid=47601256924928 size=30 diff_time:144495usec | 144ms
tid=47601254823680 size=30 diff_time:152498usec | 152ms
tid=47601256924928 size=36 diff_time:148525usec | 148ms
tid=47601254823680 size=36 diff_time:152242usec | 152ms
tid=47601256924928 size=60 diff_time:136363usec | 136ms
tid=47601254823680 size=60 diff_time:136486usec | 136ms
tid=47601256924928 size=100 diff_time:139143usec | 139ms
tid=47601254823680 size=100 diff_time:148458usec | 148ms
tid=47601256924928 size=180 diff_time:155570usec | 155ms
tid=47601254823680 size=180 diff_time:137590usec | 137ms
tid=47601254823680 size=300 diff_time:147943usec | 147ms
tid=47601256924928 size=300 diff_time:154894usec | 154ms
tid=47601254823680 size=600 diff_time:147344usec | 147ms
tid=47601256924928 size=600 diff_time:155993usec | 155ms
tid=47601254823680 size=1000 diff_time:149958usec | 149ms
tid=47601256924928 size=1000 diff_time:146590usec | 146ms
tid=47601254823680 size=1200 diff_time:140344usec | 140ms
tid=47601256924928 size=1200 diff_time:157028usec | 157ms
tid=47601254823680 size=3000 diff_time:154509usec | 154ms
tid=47601256924928 size=3000 diff_time:159357usec | 159ms
tid=47601254823680 size=5000 diff_time:148422usec | 148ms
tid=47601256924928 size=5000 diff_time:147129usec | 147ms
tid=47601254823680 size=8202 diff_time:151623usec | 151ms
tid=47601256924928 size=8202 diff_time:153929usec | 153ms
tid=47601254823680 size=16394 diff_time:146452usec | 146ms
tid=47601256924928 size=16394 diff_time:141301usec | 141ms
tid=47601254823680 size=51200 diff_time:257559usec | 257ms
tid=47601254823680 total diff_time:2651372usec | 2651ms avg=155ms
tid=47601256924928 size=51200 diff_time:243754usec | 243ms
tid=47601256924928 total diff_time:2661121usec | 2661ms avg=156ms
[ALL ASSERT PASSED -- 52094510/52094510]
<<<<<<< CASE NAME:test_realloc DESCRIBE:for test mempool realloc >>>>>>>
[ALL ASSERT PASSED -- 8/8]
<<<<<<< CASE NAME:test_log DESCRIBE:for test log system >>>>>>>
log work thread start
read log info:[2011-11-27 23:04:55][debug]:log test final
find ptr=0x7fff5036ce2d
[ALL ASSERT PASSED -- 3/3]
<<<<<<< CASE NAME:test_mbuf DESCRIBE:for test mbuf of mbuf_seek & rewind & realloc >>>>>>>
[ALL ASSERT PASSED -- 31/31]
<<<<<<< CASE NAME:test_mbuf1 DESCRIBE:for test mbuf of mbuf_push & mbuf_pop >>>>>>>
[ALL ASSERT PASSED -- 90/90]
<<<<<<< CASE NAME:test_timer DESCRIBE:for test ftimerfd >>>>>>>
[ALL ASSERT PASSED -- 4/4]
<<<<<<< CASE NAME:test_fev DESCRIBE:for test fev for create register add del methods >>>>>>>
[ALL ASSERT PASSED -- 18/18]
<<<<<<< CASE NAME:test_fev_listener DESCRIBE:for test fev listener >>>>>>>
test listener thread startup
wait for poll
net_conn:connect sucess fd = 6
[ALL ASSERT PASSED -- 5/5]
<<<<<<< CASE NAME:test_fev_buff DESCRIBE:for test fev buff >>>>>>>
wait for poll
net_conn:connect sucess fd = 6
read size=12, read_str=hello final
main recv str=hi final
evbuff error
error happened haha
[ALL ASSERT PASSED -- 22/22]
<<<<<<< CASE NAME:test_fev_conn DESCRIBE:for test fev asynchronous connect >>>>>>>
main tid=47601252718336
wait for poll
accept sucessful
tid=47601256924928
[ALL ASSERT PASSED -- 2/2]
--------------------------------------
TOTAL CASE 13, PASS 13, FAILED 0
///////
这套方案进行一次进化, 主要是改进了一些在提交版本时冲突的问题.
- 编写好code并commit到local branch
- push到autobuild server一个对应的branch上, server会将新code pull到指定的目录下
- compile & run unit test ...
- 观看实时结果, 并收到一封email
OK, 流程上很简单清晰, 但是这里有一个问题, 如果我们写好的code与server上的code已经存在了冲突, 那第二步将会失败, 导致无法继续, 作为用户, 自然不希望看到这一点, 所以我们来着手改进它.
目的明确就好办了, 下面谈谈我是如何改进的. 首先我们需要解决一旦发生了push后新代码与当前build环境的code有冲突的情况, 我们该做些什么事情, 删除分支? 可以. 换新分支继续push, 也可以, 不过这样会导致用户体验不好. 所以可以集中精力在删除分支上. 想要做到让用户没有感觉, 那么这个过程需要放在server端处理. 简单的做法, 如果我们删除了分支, 自然build环境中的code也就没有存在的价值, 因为这和用户需要build的代码可能差异很大, 我们可以先删除整个build目录, 然后重新checkout一份新的出来. OK, 简单的列一下:
- user编写code并commit到local branch
- push到autobuild server一个branch上
- autobuild server删除之前的build环境, 并重新checkout一份新的仓库代码
- 切换到对应branch上, compile & run unit test ...
- autobuild server删除对应branch的index
- user 观查实时反馈, 并最终收到一份邮件
这里面我需要解释一下:
- 第3步, 为什么是在每次接收到commit才去删除之前的build环境? -- 这主要是方便debug, 比如发现了coredump, 或者程序逻辑不正确需要上去debug, 这至少为我们留下了现场可供参考, 不至于什么都没有了.
- 第5步, 为什么删除的index, 而不是通过一条git指令比如 git push repository :branch_name 去删除? -- 这会导致我们的post-receive脚本被重新触发并执行, 容易出现错误.
好了, 来张图可能就更清楚了:
接下来再来看看我们调整过后的脚本:
- <SPAN style="FONT-SIZE: 14px">
- mailfile='autobuild.txt'
- email='xxx@xx.com'
-
- unset GIT_DIR
-
- # 1. get branch name
- while read oldrev newrev ref
- do
- echo "found pushed branch name:$ref"
- echo "oldrev=$oldrev newrev=$newrev"
- old_rev=$oldrev
- new_rev=$newrev
- refs=$ref
- done
- branch_name=`echo $refs | awk -F "/" {'print $3'}`
-
- # 2. cleanup build environment
- top_dir='xxx'
- test_dir='xxx'
- cd $top_dir
- rm -rf $test_dir
-
- # 3. checkout repository
- git clone git://127.0.0.1/your_repository
-
- # 4. switch to branch
- git checkout -b $branch_name -t origin/$branch_name
-
- # 5. build
- make --no-print-directory 2>> $mailfile
-
- # 6. run test unit
- make run_test --no-print-directory >> $mailfile
-
- # 7. output to stdout for echo back
- cat $mailfile
-
- # 8. remove branch head index
- rm -f /gitrepo_directory/refs/heads/$branch_name
-
- # 9. send a email to yourself
- mail -s "Autobuild `date`" $email < $mailfile</SPAN>