需求:做持续集成,一个核心点为编译脚本的编写,本文根据项目实际经验做一些总结。
help的重要性
一个shell脚本,如何快速上手,help至关重要,例如linux下的shell命令,通过man或–help 通常可以快速了解这个指令的用法,写编译脚本也不例外,因为很有可能后期用这个脚本的人不是当初写脚本的人。一个好的help 应该告诉使用者 如何传参数,参数的解释以及一些基本用法等。
参数解析
编译脚本应该可以适应不同的任务,不建议一个脚本对应一个编译任务,例如如果你需要编译一个debug版本的apk或者一个release版本的apk,不好的实践是你写了一个build_debug.sh 又写了一个build_release.sh, 而建议的做法是抽象到一个shell中去,做封装和参数解析,这样便于维护和扩展。如你可以这样写build.sh -t debug/release等,通过-t参数指定编译任务。shell里面的对于参数解析可以温习getopts这个指令,请自行搜索不再赘述。
参数有效性判定
在安全领域有句话叫不要相信用户的输入,对于一个脚本来说,你同样需要对用户输入做有效性校验,例如你的这个脚本只接收这些参数,其他都忽略,那么脚本中就得做相应的判断
[ "$buildType" != "debug" -a "$buildType" != "release" ] && echo "buildType needs to be debug or release" && usage
以上例子是对参数做判定,只能接受”debug”或”release”其他输入就会提示用户。
当然还有参数非空判断,见以下例子:
para_check()
{
echo "parameter error, please check......"
echo
exit 1
}
[ -z "$1" ] && para_check
日志的技巧
如果有报错,看日志是最有效的做法,shell脚本中日志也必不可少,比如关键函数的执行轨迹,异常之后的日志输出,执行时间的记录等。
build()
{
[ $# != 2 ] && para_check
echo "------build begin------"
TIME_BEFORE_UNIT=`date +%s`
rm -rf ./output
env_opt "$1"
compile "$2"
pwd && ls -lhR ./output
if [ $? -ne 0 ] ; then
echo "------build error------";
return -1;
fi
TIME_AFTER_UNIT=`date +%s`
UNIT_TIME=$(($TIME_AFTER_UNIT-$TIME_BEFORE_UNIT))
echo "------compile elapse time:" $UNIT_TIME "s"
echo "------build end------"
return 0;
}
这个例子记录了shell的执行开始时间,结束时间,执行路径等,可以方面后期debug和维护。
编译环境
这个是一个容易忽略的点,但是往往再问题定位时会给出一个思路,例如同样的代码用java1.6编译和1.7编译,编译错误很有可能是不一样的,同时一些语言自身也在不断发展,例如gradle迭代速度就非常块,经常会有稳定性,性能提升方面的改进,当你发现编译的主逻辑没有变化,但是不同的机器,编译出来的结果差异很大时,需要考虑编译环境的问题,我的习惯做法是在编译脚本开始打印出软件的版本,如java或gradle的version等。
另外一点:使你定义的环境变量生效,简单的做法先定义好相关的path 和环境变了到profie,然后source ~/.bash_profile使其生效。
一个完整的例子
#!/bin/bash
#/***************************************************************************
# *
# * Copyright (c) 2016 xxx.com, Inc. All Rights Reserved
# *
# **************************************************************************/
#/**
# * @file build_ci.sh
# * @author xxx@xx.com
# * @date 2016/11/28/
# **/
# 帮助
usage()
{
echo "-------------------------------------------------------------------------------"
echo "Usage: build_ci.sh [-r] [-t debug/release] [-f dev/prod] [-a ut/cc] ..."
echo "-------------------------------------------------------------------------------"
echo
echo "Options:"
echo " -r Turns the remote cluster compile on, By default, build task is a local build"
echo " -t Specifies options for build type : debug/release"
echo " -f Specifies options for flavor : dev/prod "
echo " -a Specifies options for build task : ut(unit test)/cc(static code check)"
echo " -s Specifies a android device(get id by exec adb devices)"
echo " -h Displays usage information for build_ci.sh"
echo
echo "Examples:"
echo "sh build_ci.sh -t debug -f dev"
echo "sh build_ci.sh -t debug -f prod"
echo "sh build_ci.sh -t release -f dev"
echo "sh build_ci.sh -t release -f prod"
echo "sh build_ci.sh -t debug -f dev -s B2T0216513013613 -a ut"
echo "sh build_ci.sh -t debug -f dev -s B2T0216513013613 -a cc"
echo
exit 1
}
para_check()
{
echo "parameter error, please check......"
echo
exit 1
}
checkVersion()
{
java -version
gradle -v
}
# 编译集群环境准备
env_opt()
{
echo "set build env......"
[ -z "$1" ] && para_check
echo "is_remote_build:$1"
if [ "$1" == true ]; then
echo "set remote build env"
export BUILD_KIT_PATH=/home/scmtools/buildkit
echo "BUILD_KIT_PATH:"$BUILD_KIT_PATH
export JAVA_HOME=$BUILD_KIT_PATH/jdk-1.8u92
export GRADLE_HOME=$BUILD_KIT_PATH/gradle/gradle-3.2.1
export PATH=$JAVA_HOME/bin:$GRADLE_HOME/bin:$PATH
fi
echo "source ~/.bash_profile>/dev/null 2>&1"
source ~/.bash_profile>/dev/null 2>&1
checkVersion
}
# 编译
compile()
{
[ -z "$1" ] && para_check
echo $1
echo "gradle compile begin"
chmod 755 ./gradlew
$($1)
echo "gradle compile end"
}
build()
{
[ $# != 2 ] && para_check
echo "------build begin------"
TIME_BEFORE_UNIT=`date +%s`
rm -rf ./output
env_opt "$1"
compile "$2"
pwd && ls -lhR ./output
if [ $? -ne 0 ] ; then
echo "------build error------";
return -1;
fi
TIME_AFTER_UNIT=`date +%s`
UNIT_TIME=$(($TIME_AFTER_UNIT-$TIME_BEFORE_UNIT))
echo "------compile elapse time:" $UNIT_TIME "s"
echo "------build end------"
return 0;
}
# 变量初始化
workspace=$(pwd)
is_remote_build=false
buildType="debug"
flavor="prod"
buildAction=""
deviceId=""
[ $# -eq 0 ] && usage
# get options
while getopts "rt:f:a:s:" arg
do
case $arg in
r)
is_remote_build=true
;;
t)
buildType=$OPTARG
echo "buildType:$buildType"
[ "$buildType" != "debug" -a "$buildType" != "release" ] \
&& echo "buildType needs to be debug or release" && usage
;;
f)
flavor=$OPTARG
echo "flavor:$flavor"
[ "$flavor" != "dev" -a "$flavor" != "prod" ] \
&& echo "flavor needs to be dev or prod" && usage
;;
a)
buildAction=$OPTARG
echo "buildAction:$buildAction"
[ "$buildAction" != "ut" -a "$buildAction" != "utup" -a "$buildAction" != "cc" ] \
&& echo "buildAction needs to be ut or cc or utup" && usage
;;
s)
deviceId=$OPTARG
echo "deviceId:$deviceId"
;;
?)
usage
;;
esac
done
buildType="$(tr '[:lower:]' '[:upper:]' <<< ${buildType:0:1})${buildType:1}"
flavor="$(tr '[:lower:]' '[:upper:]' <<< ${flavor:0:1})${flavor:1}"
build_cmd_header="./gradlew clean "
build_cmd=$build_cmd_header"assemble"$flavor$buildType
device_model="";
device_version="";
if [ ! -z "${deviceId}" ] ; then
echo "export ANDROID_SERIAL=$deviceId"
export ANDROID_SERIAL=$deviceId
echo "adb -s $deviceId shell getprop ro.product.model | xargs"
echo "adb -s $deviceId shell getprop ro.build.version.release | xargs"
device_model=$(adb -s $deviceId shell getprop ro.product.model | xargs)
device_version=$(adb -s $deviceId shell getprop ro.build.version.release | xargs)
fi
if [ ! -z "${buildAction}" -a "${buildAction}" == "ut" ] ; then
build_cmd=$build_cmd_header"create"$flavor$buildType"CoverageReport"
fi
if [ ! -z "${buildAction}" -a "${buildAction}" == "cc" ] ; then
build_cmd=$build_cmd_header"assemble"$flavor$buildType" check"
fi
echo "build_cmd:$build_cmd"
build "$is_remote_build" "$build_cmd"