使用crontab進行Android代碼的自動更新和構建

引子

最近的工作是一個在Android平臺上進行開發的項目,我個人基本是不改動Android部分的代碼,但是我所在的項目需要使用到Android編譯出來的很多目標文件。另一方面,我又不是開發apk等基於通用Android平臺的項目,即Android部分的代碼是有其他同事在進行維護。那麼就會有這樣的場景:我需要保持Android部分代碼的更新和並構建出來。

編譯過整個Android工程的人都知道編譯一次的時間大概要30分鐘以上(如果你是獨佔服務器且內存超大,那麼請默默走開~),要是整個工程全部進行重編(加上-B參數),則耗時至少在2個小時以上。假設你想要加速編譯的過程而使用 -jN參數時,那你就等着被其他同事罵吧(因爲會拖的別人完全無法進行任何操作)。

對於我的工作來講,要求保持Android部分代碼的更新,但是時效性並沒有太強,即只要Android部分代碼有改動了,我這邊抽空的時候更新下載並編譯出來即可。是的,只需要抽空完成這個操作即可,但是作爲一個程序員一定知道的,如果要抽空完成一件事,那麼這件事就基本上遙遙無期了。

因此,我就想何不搞一個自動更新並構建的腳本,讓這個工作在晚上沒人的時候靜悄悄的完成,這就有了下面的腳本和這篇文章。

crontab_android-4.2.1_r1.sh

先看一下這個腳本的具體內容:

#! /bin/bash

ANDROID_4_2=~/android-4.2.1_r1
CRONTAB_LOG_FOLDER=~/bak/crontablog
NONEED_TO_REVERT=build/core/main.mk

cd $ANDROID_4_2

HEAD_VER=`svn log -r HEAD | awk '{if(NR==2)print $1}' | cut -b 2-`
BASE_VER=`svn log -r BASE | awk '{if(NR==2)print $1}' | cut -b 2-`
DATE=`date "+%Y-%m-%d"`

if [ $HEAD_VER -eq $BASE_VER ]; then 
    echo "$DATE: current version($BASE_VER) is the same with svn server($HEAD_VER), no need to build system" >> $CRONTAB_LOG_FOLDER/crontab_log.txt
else
    echo "$DATE: current version($BASE_VER) is different with svn server($HEAD_VER), need to build system" >> $CRONTAB_LOG_FOLDER/crontab_log.txt
    BUILD_LOG=$CRONTAB_LOG_FOLDER/${ANDROID_4_2##*/}_build_$DATE.log
    svn up --force > $BUILD_LOG
    svn st -q | grep ^M | grep -v $NONEED_TO_REVERT | awk '{print $2}' | xargs -i svn revert {} >> $BUILD_LOG
    source build/envsetup.sh >> $BUILD_LOG
    make >> $BUILD_LOG 2>& 1
fi

下面就對該腳本進行分解。

第一部分

#! /bin/bash

之所以把這一行單獨拿出來講解,是因爲在Ubuntu下,默認的sh已經被替換成了dash,而dash是一種非常符合POSIX標準的Unix Shell,其帶來的影響就是"原先在bash shell 下可以運行的shell script ,會出現一些意想不到的問題,不是100%的兼用。"。而Bash是我們比較熟悉的,其語法相對也比較寬泛。

而這裏使用bash的根本原因則是,在編譯Android之前,需要使用下述命令來配置環境:

source build/envsetup.sh

而我發現,如果使用默認的sh來解析和執行腳本,那麼這一行命令放到腳本中竟然無法執行,錯誤提示如下:

./crontab_android-4.2.1_r1.sh: 20: ./crontab_android-4.2.1_r1.sh: source: not found

修改成#! /bin/bash後,就可以順利執行。至於具體bash和dash的區別可以參考《bash與dash的差別

第二部分

ANDROID_4_2=~/android-4.2.1_r1
cd $ANDROID_4_2

HEAD_VER=`svn log -r HEAD | awk '{if(NR==2)print $1}' | cut -b 2-`
BASE_VER=`svn log -r BASE | awk '{if(NR==2)print $1}' | cut -b 2-`

首先需要進入到Android的源碼目錄,並獲取到服務器上的最新版本號和本地的版本號。注意,這裏分別是用了HEAD和BASE來表示服務器上的最新版本和當前工作副本的版本。關於HEAD和BASE的解釋具體見svn log --help:

  -r [--revision] ARG      : ARG (some commands also take ARG1:ARG2 range)
                             A revision argument can be one of:
                                'HEAD'       latest in repository
                                'BASE'       base rev of item's working copy

而通過svn log -r HEAD或svn log -r BASE獲取的版本信息格式如下:

------------------------------------------------------------------------
r100 | nferzhuang | 2014-12-26 15:06:44 +0800 (Fri, 26 Dec 2014) | 2 lines

This is a test version

------------------------------------------------------------------------

因此,需要通過awk讀取第二行的第一個字段,並且使用cut截取第2個字符到結尾的部分。(PS:這一小塊的處理不是很好,後續需要優化一下)

第三部分

if [ $HEAD_VER -eq $BASE_VER ]; then 
    echo "$DATE: current version($BASE_VER) is the same with svn server($HEAD_VER), no need to build system" >> $CRONTAB_LOG_FOLDER/crontab_log.txt
else
    echo "$DATE: current version($BASE_VER) is different with svn server($HEAD_VER), need to build system" >> $CRONTAB_LOG_FOLDER/crontab_log.txt
    BUILD_LOG=$CRONTAB_LOG_FOLDER/${ANDROID_4_2##*/}_build_$DATE.log
    svn up --force > $BUILD_LOG
    svn st -q | grep ^M | grep -v $NONEED_TO_REVERT | awk '{print $2}' | xargs -i svn revert {} >> $BUILD_LOG
    source build/envsetup.sh >> $BUILD_LOG
    make >> $BUILD_LOG 2>& 1
fi

再得到服務器上的最新版本號和本地的版本號後,通過比較它們就知道是否需要進行代碼更新,如果不需要則直接記錄一下log到指定文件中即可。否則就需要更新代碼並進行編譯。

這裏先講一下BUILD_LOG,即Log文件的名稱是如何組成的。

    ANDROID_4_2=~/android-4.2.1_r1    
    DATE=`date "+%Y-%m-%d"`
    BUILD_LOG=$CRONTAB_LOG_FOLDER/${ANDROID_4_2##*/}_build_$DATE.log

DATE的獲取可以參考date命令的man手冊,這裏主要描述一下linux中shell截取字符串的方法,具體請參考文章《linux中shell截取字符串方法總結》

使用 ## 號操作符。用途是從左邊開始刪除最後一次出現子字符串即其左邊字符,保留右邊字符。用法爲##*substr,例如:
str='http://www.baidu.com/cut-string.html'
echo ${str##*/}
得到的結果爲cut-string.html,即刪除最後出現的"/"及其左邊所有字符
注:如果是echo ${str##*//},輸出結果與echo ${str#*//}一樣,因爲str中只有一個"//"字符串

我咋這裏主要的作用是截取$ANDROID_4_2字符串的最後一個字段,然後和_build_$DATE.log組裝成一個文件名。

下面的一個命令也單獨需要講一下:

    svn st -q | grep ^M | grep -v $NONEED_TO_REVERT | awk '{print $2}' | xargs -i svn revert {} >> $BUILD_LOG

先說一下這行代碼的作用:講當前文件夾下所有改動的文件(除$NONEED_TO_REVERT文件)還原。分解一下就是:

  • svn st -q:獲取一下當前的svn改動的摘要信息,注意此處使用-q參數的作用是不顯示不在版本管理中的文件
  • grep ^M:過濾出所有M開頭的行,即有改動的文件
  • grep -v $NONEED_TO_REVERT:在過濾出來的文件中不包括指定的$NONEED_TO_REVERT文件
  • awk '{print $2}':對每一行打印出第二個字段
  • xargs -i svn revert {}:對每一行的結果執行svn revert操作,關於xargs -i參數的使用請參考文章《xargs的i參數

最後一個需要講解的代碼是:

    make >> $BUILD_LOG 2>& 1

這裏使用2>& 1的作用是:不僅把標準輸出的結果重定向到文件中,也把錯誤輸出的結果重定向到文件。具體請參考文章:《2>&1使用

crontab簡述

介紹完了crontab_android-4.2.1_r1.sh腳本的內容,如果讓這個腳本按照計劃的時間運行起來就是crontab的工作了。先看一下百度百科中對於crontab的定義:

crontab命令常見於Unix和類Unix的操作系統之中,用於設置週期性被執行的指令。該命令從標準輸入設備讀取指令,並將其存放於"crontab"文件中,以供之後讀取和執行。該詞來源於希臘語chronos(χρνο),原意是時間。通常,crontab儲存的指令被守護進程激活,在後臺運行,每分鐘檢查一次是否有預定的作業需要執行。這類作業一般稱爲cron jobs。

crontab作業的格式如下:
*  *  *  *  *  command
分 時 日 月 周 命令
第1列表示分鐘1~59 每分鐘用*或者 */1表示
第2列表示小時1~23(0表示0點)
第3列表示日期1~31
第4列表示月份1~12
第5列標識號星期0~6(0表示星期天)
第6列要運行的命令

crontab命令的使用方法是:

usage:    crontab [-u user] file
    -e    (edit user's crontab)
    -l    (list user's crontab)

簡單來講就是通過-e參數進行編輯,通過-l參數進行查看。具體編輯的方法不講,下面看一下我的crontab作業表:

#00 00 * * * date >> ~/bak/date.txt
00 00 * * * sh ~/bin/crontab_android-4.2.1_r1.sh &

上面一行是用來進行測試,確保每天凌晨的時候可以開始執行任務,下面的一行就是添加了每天00:00的時候執行crontab_android-4.2.1_r1.sh腳本。

這裏爲什麼使用&來強制進行後臺運行,主要的原因是因爲該腳本中是一個阻塞的工作,不應該獨佔crontab任務表的執行。

總結

這一篇博客實際上並不太好,涉及到了crontab、svn、shell等好幾個方面,每一個單獨拿出來都是厚厚的一本書,在這裏我是使用他們組合完成一個自動化的動作。

最後要收的一句就是:能夠製造和使用工具是人和動物的本質區別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章