首页 > 代码库 > 使用crontab进行Android代码的自动更新和构建

使用crontab进行Android代码的自动更新和构建

引子

最近的工作是一个在Android平台上进行开发的项目,我个人基本是不改动Android部分的代码,但是我所在的项目需要使用到Android编译出来的很多目标文件。另一方面,我又不是开发apk等基于通用Android平台的项目,即Android部分的代码是有其他同事在进行维护。那么就会有这样的场景:我需要保持Android部分代码的更新和并构建出来。

编译过整个Android工程的人都知道编译一次的时间大概要30分钟以上(如果你是独占服务器且内存超大,那么请默默走开~),要是整个工程全部进行重编(加上-B参数),则耗时至少在2个小时以上。假设你想要加速编译的过程而使用 -jN参数时,那你就等着被其他同事骂吧(因为会拖的别人完全无法进行任何操作)。

对于我的工作来讲,要求保持Android部分代码的更新,但是时效性并没有太强,即只要Android部分代码有改动了,我这边抽空的时候更新下载并编译出来即可。是的,只需要抽空完成这个操作即可,但是作为一个程序员一定知道的,如果要抽空完成一件事,那么这件事就基本上遥遥无期了。

因此,我就想何不搞一个自动更新并构建的脚本,让这个工作在晚上没人的时候静悄悄的完成,这就有了下面的脚本和这篇文章。

crontab_android-4.2.1_r1.sh

先看一下这个脚本的具体内容:

<script src="https://code.csdn.net/snippets/562508.js" type="text/javascript"></script>

下面就对该脚本进行分解。

第一部分

#! /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等好几个方面,每一个单独拿出来都是厚厚的一本书,在这里我是使用他们组合完成一个自动化的动作。

最后要收的一句就是:能够制造和使用工具是人和动物的本质区别

使用crontab进行Android代码的自动更新和构建