Android Framework 批量编译-批量推送 脚本

刚毕业时在某手机厂商任职 Android Framework 研发工程师,工作了一段时间后,深感 Framework 层的编译调试太繁琐,每次都做重复劳动.
因此决定要做一个一揽子解决方案,将整个 编译-打包-安装至手机 的流程用一行命令搞定. 于是写了一年多,写完了这个最终近2000行的脚本.
这是题主从零开始学写 bash 的最初的原因.
现在不再做 Framework 开发了,也再也没哪个脚本写的比这个罗辑更多了.最近整理脚本,觉得不分(xian)享(bai)一下太对不起这脚本了.

需求:

做过 Framework 开发的同学都知道, Framework 层中基本是一个模块一个 Android.mk, 最终的编译生成物有一个的,也有多个的. 编译产物会被放在项目根目录下的固定目录,其路径就对应着这个编译生成物应该推送至手机内的那个路径.
一般的调试流程是:
修改代码 -> 找到模块根目录编译模块 -> 编译 -> 在编译日志中找到编译产物路径 -> 根据编译产物路径将生成物 adb push 到手机
有时一个模块生成多个lib (我记得 installd 模块会生成7个),少推一个手机就起不来了.
有时一个修改涉及多个模块,那上面的步骤就要重复多次.
我们厂当时代码都放在了服务器上,编译完成后,要把编译生成物放在一个中间服务器(便于审核),再从中间服务器下载到本机,再从本机 push 到手机.那这个流程就又延长且繁琐了,最终每一次编译调试都要五分钟左右,还经常性的出错.
另外系统签名肯定是要放在服务器的,要签名就得上传到签名服务器. 某些模块没有签名的话,直接推也会让手机起不来.

最终要实现的脚本要完成下面事情:

  1. 支持指定某个文件为参数,自动找到相应的 Android.mk 编译之
  2. 支持指定多个上述文件.多个文件对应一个 Android.mk 时, duplicate 之. 对应多个时,逐个编译之
  3. 分析编译日志,找到所有编译产物路径
  4. 分析编译产物路径,并用 adb push 逐个将编译产物推送至手机

由于我们厂又多了个中间服务器中转过程,在以上步骤3之后的需求改为如下:

  1. 将解析得到的信息汇总,生成 adbpush.sh 的脚本,内含各个编译产物要推送到手机内的路径
  2. 将编译产物及 adbpush.sh 打包,并上传至中间服务器
  3. 本机编写 adb_auto_push.sh 脚本,从中间服务器下载上一步的压缩包
  4. 解压并对需要签名的模块签名(到签名服务器)
  5. 执行压缩包内的 adbpush.sh 脚本,将编译产物推送至手机

基于中转服务器,又写了一个脚本,实现轮询中转服务器(每5秒),有新的符合规则的压缩包则下载并推送.这样看起来在服务器转一手看起来就和在本机编译体验一样了.
这个脚本最终完成后,极大的提高了相关工作效率和准确度.

代码:

公司中转服务器的那个脚本就不贴了,这里只贴在本机编译 Framework 并推送的那个脚本.
已经离开Framework相关工作太久,逐一分析太费时间了,这里就偷懒只贴代码了.
这个脚本其实是不限工作环境的,对于现在的 Android 版本,应该稍加修改也还可以用.

#!/bin/bash

#       AUTHOR : liuxu
#       THIS SHELL IS ENVIRONMENT INDEPENDENT

#v1.0   compile and push libs to phone
#       $1 should be file or dir you wish to compile
#       without $1 this sh will compile the current dir
#v1.1   add error code for exit
#       1:     pre-compile error
#       2:     adb error
#       3:     post-compile error, most commonly caused by various compile error
#       4:     adb errors, may be caused by "device not found", not critical
#       5:     compile error
#v1.2   add -t option: touch file before compile. the sh will decide which file to touch, usually the last modified file.
#v2.0   2012-03-28
#       add support for multi-dir/file param
#       add -l option: use the given num as "lunch" command param
#v2.1   2012-04-12
#       add support for "choosecombo" when setting up compile environment
#v2.2   2012-04-19
#       add -d option: enable DEBUG
#v2.3   2012-07-03
#       debug: when LocateAndroidmk() return $PROJECT_PATH, exit sh, or it will run "make" in project root dir.
#v2.4   2012-11-28
#       1. another way to locate $PROJECT_PATH:
#          run "source build/envsetup.sh", if the return value is 0, than the $PROJECT_ROOT dir is located.
#v3.0   2013-01-17
#       this sh is now independent to environment. changes:
#       1. another way to locate $PROJECT_PATH:
#          just follow "gettop": see if "build/core/envsetup.mk" and "Makefile" exists.
#       2. use getopt to process params.
#       3. change the way of process params to let this shell be less dependent to environment.
#       4. when compile many files, print info after compile even if one of the files failed to compile.
#          this is to ensure that we know which modules has already been compiled.
#v3.1   2013-02-01
#       debug: locate wrong project root dir sometimes
#v3.2   2013-02-05
#       add -u option:
#       make update-api after compile
#v3.3   2013-04-15
#       print current time before exit

#====================================
#global variables

#I've exported this var through .bashrc. so just commit the code here. 
#DEFAULT_CONFIG_TAG="Config"

CURRENT_PATH=`pwd`                  #
PROJECT_NAME=                       #
PROJECT_PATH=                       #
LUNCH_NUM=
ERROR_NUM=0                         #mark for error number if an error occure, also can be used as "exit" return number
B_TOUCH_ENABLED="false"             #mark for use touch command before compile
B_ADB_REMOUNT="false"
B_COMPILE_SUCCEED="true"
B_PUSH_TO_PHONE="true"
B_UPDATE_API="false"
DEBUG="false"

SH_DOC=$(dirname $0)"/sh_document"
SH_DOCUMENT=$SH_DOC/document$(date +%m%d)
COM_LIBS_STORE=$SH_DOCUMENT/compiled_store  #used to store compiled file path
TMP_DIR=/tmp/compile_and_push_info          #temp file to store mmm output info

#this array should follow the below format:
#src_file:::compiled_lib:::phone_dir
declare -a MODULE_ARR               #modules generated by compile
MODULE_ARR_LENGTH=${#MODULE_ARR[*]}

#this array should follow the below format:
#src_file:::mk_path
declare -a SRC_ARR                  #src file or dir
SRC_ARR_LENGTH=${#SRC_ARR[*]}

#this array is used to cache duplicated src file
#if two src_file have the same mk_path, one of them is marked as duplicated
#element should be path based on PROJECT_PATH
declare -a DUPLICATED_SRC_ARR
DUPLICATED_SRC_ARR_LENGTH=${#DUPLICATED_SRC_ARR[*]}

#====================================
#util functions

trap "CLEAR_WORK" EXIT

DEBUG() {
    if [ "$DEBUG" == "true" ]; then
        $@
    fi
}

CLEAR_WORK() {
    if [ -e $TMP_DIR ]; then
        sudo rm -rf $TMP_DIR
    fi

    local current_time="`date +%x`  `date +%T`"
    echo "* Time on exit:  $current_time"
    echo
}

function ShellHelp() {
cat <<EOF

--------------------------------------------------------------------------------
USAGE:
compile_and_push.sh [-t] [-x] [-d] [-u] [-l lunch_number] PATH

OPTIONS:
-t : touch the the newest or the given file before compile
-l : use the given number as 'lunch' arg
-d : enable DEBUG, the debug logs will be print
-x : do not push libs to phone after compile
-u : make update-api after compile

DESCRIPTION:
Compile a module based on the given files and dirs, and then push the module(s) to the phone.
By default, the compiled modules will be pushed to the phone.
This shell only compile files or dirs in the same project path.
Note that the options should always come before params.

RETURN CODE:
When things' not all right, this sh will return ERROR CODE as below:
  1:     pre-compile error
  2:     adb error
  3:     post-compile error, most commonly caused by various compile error
  4:     adb errors, may be caused by 'device not found', not critical
  5:     compile error
  6:     input file in different project dir
--------------------------------------------------------------------------------

EOF
}

#see if $1 is interger or not
#if $2, $3 is presented, see if $1 is inside [$2, $3]
#yield true or false
#if present, $2 and $3 should be interger
function IsInterger() {
    local ret       #return value

    if [[ $1 =~ [0-9]+ ]]; then     #make sure input is interger
        ret="true"
    else
        ret="false"
    fi

    if [ "$ret" == "false" -o $# -eq 1 ]; then
        echo $ret
        return
    fi

    if [[ ( $1 -ge $2 ) && ( $1 -le $3 ) ]]; then      #make sure $n is inside the range
        ret="true"
    else
        ret="false"
    fi

    echo $ret
}

#see if $1 is one of the files below:
# c, cpp, h, java, xml, rc
#yield true or false
#$1 should be a full path of a file
function IsAndroidSrcFile() {
    local ret="false"
    if [ ! -f $1 ]; then
        echo "false"
        return
    fi

    local bn=$(basename $1)
    local suffix=${bn#*.}

    if [ "$suffix" == "c" -o \
         "$suffix" == "cpp" -o \
         "$suffix" == "h" -o \
         "$suffix" == "java" -o \
         "$suffix" == "xml" -o \
         "$suffix" == "rc" ]; then
        ret="true"
    else
        ret="false"
    fi

    echo $ret
}

#locate the nearest Android.mk file in the upper dirs
#$1 should be a full path of a file or dir
#without $1 this function will check current path
#yield the path if found, or "/" if not found
function LocateAndroidmk() {
    local path
    local cur_path=$(pwd)
    local ret_path

    if [ $# -eq 0 ]; then
        path=$(pwd)
    elif [ -f $1 ]; then
        path=$(dirname $1 | xargs readlink -f)
    else
        path=$(readlink -f $1)
    fi

    ret_path=$path
    cd $ret_path > /dev/null
    while true; do
        #DEBUG echo "LocateAndroidmk, path: $path; ret path: $ret_path"
        if [ -f $ret_path/Android.mk ]; then
            break
        elif [ "$ret_path" == "/" ]; then
            echo "/"
            return
        fi
        cd ..
        ret_path=$(pwd)
    done

    cd $cur_path > /dev/null
    echo $ret_path
}

#locate project root dir
#$1 should be a full path of a file or dir
#without $1 this function will check current path
#yield the path if found, or "/" if not found
function LocateProjectRoot() {
    local path
    local cur_path=$(pwd)
    local tmp_path
    local prj_path
    
    if [ $# -eq 0 ]; then
        path=$(pwd)
    elif [ -f $1 ]; then
        path=$(dirname $1 | xargs readlink -f)
    else
        path=$(readlink -f $1)
    fi

    tmp_path=$path
    cd $tmp_path > /dev/null
    while true; do
        if [ -f build/core/envsetup.mk -a -f Makefile ]; then
            break
        elif [ "$tmp_path" == "/" ]; then
            echo "/"
            return
        fi
        cd ..
        tmp_path=$(pwd)
    done

    cd $cur_path > /dev/null
    prj_path=$tmp_path
    if [ $? -eq 0 ]; then
        echo $prj_path
    else
        echo "/"
    fi
}

function PrintVariableInfo() {
    [ "$DEBUG" == "false" ] && return

    echo "===================================="
    echo "PROJECT_NAME=$PROJECT_NAME"
    echo "PROJECT_PATH=$PROJECT_PATH"
    echo "LUNCH_NUM=$LUNCH_NUM"
    echo "B_TOUCH_ENABLED=$B_TOUCH_ENABLED"
    echo "B_PUSH_TO_PHONE=$B_PUSH_TO_PHONE"
    echo "B_ADB_REMOUNT=$B_ADB_REMOUNT"

    for src in ${SRC_ARR[*]}; do
        echo "SRC_ARR : $src"
    done

    for module in ${MODULE_ARR[*]}; do
        echo "MODULE_ARR : $module"
    done
    echo "===================================="
}

#====================================
#env related functions

#get touch file between two dirs
#only find files in $1 and superior dir of $1, until we reach $2
#$1 and $2 should be full path of a dir
#$1 should be subdir of $2
function PickTouchFile() {
    local ret=""
    if [ ! -d $1 -o ! -d $2 ]; then
        echo $ret
        return
    fi

    local begin_file=$(readlink -f $1)
    local end_file=$(readlink -f $2)

    if [[ "$begin_file" != "$end_file"* && ! $begin_file -ef $end_file ]]; then
        echo $ret
        return
    fi

    local bn; local suffix
    local current_dir=$begin_file
    local end_dir=$(cd $end_file/.. ; pwd)

    while [ ! $end_dir -ef $current_dir ]; do
        DEBUG echo "DEBUG, search in $current_dir"
        cd $current_dir
        local tmp_arr=$(find $current_dir -maxdepth 1 -type f)
        if [ ${#tmp_arr[*]} -eq 0 ]; then
            #no file type in current dir, continue
            continue
        fi
        touch_file_arr=($(find $current_dir -maxdepth 1 -type f | xargs ls -1t | sed '/\/.git\//d; /Android.mk/d' | sed -n '1,10'p))

        if [ "$DEBUG" == "true" ]; then
            for tfa in ${touch_file_arr[*]}; do
                echo "DEBUG, SrcArrAdd, touch_file_arr : $tfa"
            done
        fi

        for fff in ${touch_file_arr[*]}; do
            bn=$(basename $fff)
            suffix=${bn#*.}
            if [ "$suffix" == "c" -o \
                 "$suffix" == "cpp" -o \
                 "$suffix" == "h" -o \
                 "$suffix" == "java" -o \
                 "$suffix" == "xml" -o \
                 "$suffix" == "rc" ]; then
                ret=$fff
                echo $ret
                return
            fi
        done

        current_dir=$(cd .. ; pwd)
    done

    unset touch_file_arr
    echo $ret
    return
}

#add element to SRC_ARR
#src_file:::mk_path
#src_file & mk_path are paths based on $PROJECT_PATH
#$1 should be full path of src_file
function SrcArrAdd() {
    DEBUG echo "+++++++++++++++++++++++++++"
    DEBUG echo "DEBUG, SrcArrAdd, \$1=$1"
    if [ ! -e $1 ]; then
        echo
        echo "* SrcArrAdd, $1 does not exist. should be full path of a dir or file."
        return 1
    fi

    local mk_path=$(LocateAndroidmk $1)
    DEBUG echo "DEBUG, SrcArrAdd, mk_path=$mk_path"
    
    if [ "$mk_path" == "/" ]; then
        echo
        echo "* SrcArrAdd, could not locate a validate Android.mk for the below path, pls check."
        echo $1
        echo
        ERROR_NUM=1
        exit $ERROR_NUM
    fi
    
    local touch_file
    #touch the newest file in $1
    #if no file exist, touch the newest file in mk_path
    if [ "$B_TOUCH_ENABLED" == "true" ]; then
        if [ -f $1 ]; then
            touch_file=$1
        else
            #cache an arr of the 10 newest files
            #so that if the first newest file could not be used as touch file, use the second, etc.
            touch_file=$(PickTouchFile $1 $mk_path)
            #exit the shell here if DEBUG and TOUCH is enabled, or else it will bring more unnecessary errors
            DEBUG exit $ERROR_NUM
        fi

        DEBUG echo "DEBUG, SrcArrAdd, for $1, touch_file=$touch_file"
        if [ -f $touch_file ]; then
            touch $touch_file
        else
            echo "* fail to locate a proper file to touch."
            echo "* possibly cd to a more specified dir (sub dir of current dir) will solve the problem."
            echo "* it is best if you would use a file type as param."
            ERROR_NUM=1
            exit $ERROR_NUM
        fi
    fi

    local param_full_path=$(readlink -f $1)
    local pj_mk_path=$(echo $mk_path | awk -F "$PROJECT_PATH/" '{print $2}')
    local pj_src_file=$(echo $param_full_path | awk -F "$PROJECT_PATH/" '{print $2}')

    SRC_ARR[$SRC_ARR_LENGTH]="$pj_src_file:::$pj_mk_path"
    SRC_ARR_LENGTH=${#SRC_ARR[*]}
    DEBUG echo "+++++++++++++++++++++++++++"
}

#====================================
#compile related functions

#setup compile environment
function SetupEnv() {
    if [ ! -f $PROJECT_PATH/$PROJECT_NAME.$DEFAULT_CONFIG_TAG ]; then
        echo
        echo "* cannot find .config file !!!"
        ERROR_NUM=1
        return $ERROR_NUM
    fi

    cd $PROJECT_PATH > /dev/null
    . $PROJECT_PATH/build/envsetup.sh

    if [ "$LUNCH_NUM" == "" ]; then
        LUNCH_NUM=$(sed -n '/^DEFAULT_LUNCH_NUM/'p $PROJECT_PATH/$PROJECT_NAME.$DEFAULT_CONFIG_TAG | awk -F "=" '{print $2}')
        COMBO=$(sed -n '/^DEFAULT_COMBO/'p $PROJECT_PATH/$PROJECT_NAME.$DEFAULT_CONFIG_TAG | awk -F "=" '{print $2}')
        if [ ! "$LUNCH_NUM" == "" ]; then
            lunch $LUNCH_NUM
        elif [ ! "$COMBO" == "" ]; then
            choosecombo $COMBO
        else
            echo
            echo "* no validate option for 'lunch' command. use -l to specify a lunch number."
            ShellHelp
            ERROR_NUM=1
            return $ERROR_NUM
        fi
    fi
    
    cd - > /dev/null
}

#add element to MODULE_ARR
#src_file:::compiled_lib:::phone_dir
#src_file & compiled_lib are paths based on $PROJECT_PATH
#$1 should be full path of src_file
#$2 should be full path of compiled_lib
function ModuleArrAdd() {
    if [ ! -e $1 ]; then
        echo
        echo "* ModuleArrAdd, $1 does not exist. should be full path a file"
        return 1
    fi

    local pj_src_file=$(echo $1 | awk -F "$PROJECT_PATH/" '{print $2}')
    local pj_compiled_lib=$(echo $2 | awk -F "$PROJECT_PATH/" '{print $2}')

    local product_name=$(echo $pj_compiled_lib | awk -F "/" '{print $4}')
    local file_basename=$(basename $2)
    local phone_dir=$(echo $2 | awk -F "out/target/product/$product_name" '{print $2}' | awk -F "$file_basename" '{print $1}')

    MODULE_ARR[$MODULE_ARR_LENGTH]="$pj_src_file:::$pj_compiled_lib:::$phone_dir"
    MODULE_ARR_LENGTH=${#MODULE_ARR[*]}
}

#compile all elements in SRC_ARR and decipher compile info
#src_file:::compiled_lib:::phone_dir
function DecipherCompileInfo() {
    local mk_dir; local pj_file ;local tmp_file
    [ -d $TMP_DIR ] || mkdir $TMP_DIR
    local compiled_lib_arr
    local uniq_mk_arr
    local uniq_mk_arr_length
    local is_mk_uniq="true"

    for n in ${SRC_ARR[*]}; do
        mk_dir=$(echo $n | awk -F ":::" '{print $2}')
        pj_file=$(echo $n | awk -F ":::" '{print $1}')
        tmp_file=$(basename $PROJECT_PATH/$pj_file)"_compile_info_"$(date +%m%d%H%M%S)

        #duplicate src_file with the same mk_path
        DEBUG echo "DEBUG, mk_dir : $mk_dir ; uniq_mk_arr : ${uniq_mk_arr[*]}"
        for uma in ${uniq_mk_arr[*]}; do
            if [ $PROJECT_PATH/$uma -ef $PROJECT_PATH/$mk_dir ]; then
                is_mk_uniq="false"
                DUPLICATED_SRC_ARR[$DUPLICATED_SRC_ARR_LENGTH]=$pj_file
                DUPLICATED_SRC_ARR_LENGTH=${#DUPLICATED_SRC_ARR[*]}
                break
            fi
        done

        DEBUG echo "DEBUG, DUPLICATED_SRC_ARR : ${DUPLICATED_SRC_ARR[*]}"
        DEBUG echo "is_mk_uniq : $is_mk_uniq"
        if [ "$is_mk_uniq" == "false" ]; then
            is_mk_uniq="true"
            continue
        fi

        #compile
        if [ "$mk_dir" == "" -o "$mk_dir" == "/" ]; then
            echo "* Should not compile in root dir of a project !"
            ERROR_NUM=1
            exit $ERROR_NUM
        fi
        echo "--------------------------------------------------------------------------------"
        echo "compile ....."
        echo "src file  : $pj_file"
        echo "mk path   : $mk_dir"
        echo "--------------------------------------------------------------------------------"
        echo
        cd $PROJECT_PATH/$mk_dir > /dev/null
        #TODO: perhaps we should use "make" instead of such command as "mm"
        mm | tee -a $TMP_DIR/$tmp_file
        if [ "$B_UPDATE_API" == "true" ]; then
            echo
            echo "make update-api"
            echo
            cd $PROJECT_PATH
            make update-api
        fi
        cd - > /dev/null
        echo

        #duplicate src_file with the same mk_path
        uniq_mk_arr[$uniq_mk_arr_length]=$mk_dir
        uniq_mk_arr_length=${#uniq_mk_arr[*]}

        #decipher
        compiled_lib_arr=($(cat $TMP_DIR/$tmp_file | sed -n '/^Install: /p' | awk -F ": " '{print $2}'))

        if [ "$DEBUG" == "true" ]; then
            for cla in ${compiled_lib_arr[*]}; do
                DEBUG echo "DEBUG, decipher libs from compile info: $cla"
            done
        fi

        if [ ${#compiled_lib_arr[*]} -eq 0 ]; then
            B_COMPILE_SUCCEED="false"
            echo "--------------------------------------------------------------------------------"
            echo "* Error occur while compile below file or dir. pls check it."
            echo "$PROJECT_PATH/$pj_file"
            echo
            JustShowInfo
            ERROR_NUM=5
            exit $ERROR_NUM
        fi

        if [ ! -d $SH_DOC ]; then
            mkdir $SH_DOC
        fi

        if [ ! -d $SH_DOCUMENT ]; then
            mkdir $SH_DOCUMENT
        fi

        for m in ${compiled_lib_arr[*]}; do
            DEBUG echo "DEBUG, PROJECT_PATH/pj_file : $PROJECT_PATH/$pj_file"
            DEBUG echo "DEBUG, PROJECT_PATH/m       : $PROJECT_PATH/$m"
            ModuleArrAdd $PROJECT_PATH/$pj_file $PROJECT_PATH/$m
            echo $PROJECT_PATH/$m >> $COM_LIBS_STORE
        done
    done
}

#====================================
#adb related functions

function ReadyADBRemount() {
    [ "$UID" = "0" ] && SUDO= || SUDO=sudo
#    if [ -f $PROJECT_PATH/out/host/linux-x86/bin/adb ]; then
#        ADB="$SUDO $PROJECT_PATH/out/host/linux-x86/bin/adb"
#    else
#        ADB="$SUDO /usr/local/bin/adb"
#    fi
    ADB="$SUDO /usr/local/bin/adb"
    DEBUG echo "ADB: $ADB"

    local adb_info1=$($ADB remount | sed -n '$'p)
    local retry_count=1     #just retry once
    local i=0
    while [ $retry_count -ne $i ]; do
        echo "$adb_info1"
        if [ "$adb_info1" == "remount succeeded" ]; then
            B_ADB_REMOUNT="true"
            return
        else
            echo "* retry remount."
            $ADB kill-server
            $ADB root
            adb_info1=$($ADB remount | sed -n '$'p)
        fi
        let i++
    done

    echo "* cannot remount, please push libs manually."
    ERROR_NUM=4
    return
}

function JustShowInfo() {
    local src_file
    local phone_dir                        #to where we will put the compiled file
    local compiled_lib

    if [ $ERROR_NUM -ne 0 -a $ERROR_NUM -ne 4 ]; then
        echo
        echo "* some problem happened while running this sh. better not push any libs to the phone. pls check. ERROR_NUM=$ERROR_NUM"
        echo
        exit $ERROR_NUM
    fi

    if [ $MODULE_ARR_LENGTH -eq 0 ]; then
        echo
        echo "* No module generated, exit."
        echo
        ERROR_NUM=3
        exit $ERROR_NUM
    fi

    #display duplicated src arr
    echo "--------------------------------------------------------------------------------"
    
    if [ $DUPLICATED_SRC_ARR_LENGTH -ne 0 ]; then
        echo
        for ddd in ${DUPLICATED_SRC_ARR[*]}; do
            echo "* Duplicate Src File : $ddd"
        done
    fi

    for n in ${MODULE_ARR[*]}; do
        src_file=$(echo $n | awk -F ":::" '{print $1}')
        compiled_lib=$(echo $n | awk -F ":::" '{print $2}')
        phone_dir=$(echo $n | awk -F ":::" '{print $3}')

        if [ "$compiled_lib" == "" ]; then
            echo "* no .so or .jar etc. was generated from below file or dir. pls check it."
            echo "$src_file"
            continue
        fi

        echo
        echo "* Project name           : $PROJECT_NAME"
        echo "* Src File or Dir        : $src_file"
        echo "* File Got From Compile  : $compiled_lib"
        echo "* using below command to push to phone: "
        echo "sudo adb push $PROJECT_PATH/$compiled_lib $phone_dir"
        echo
        echo "--------------------------------------------------------------------------------"
    done
}

function PushToPhone() {
    local src_file
    local phone_dir                        #to where we will put the compiled file
    local compiled_lib

    if [ $ERROR_NUM -ne 0 -a $ERROR_NUM -ne 4 ]; then
        echo
        echo "some problem happened while running this sh. better not push any libs to the phone. pls check. ERROR_NUM=$ERROR_NUM"
        echo
        exit $ERROR_NUM
    fi

    if [ $MODULE_ARR_LENGTH -eq 0 ]; then
        echo
        echo "* No module to be pushed, exit."
        echo
        ERROR_NUM=3
        exit $ERROR_NUM
    fi

    #display duplicated src arr
    if [ $DUPLICATED_SRC_ARR_LENGTH -ne 0 ]; then
        echo
        for ddd in ${DUPLICATED_SRC_ARR[*]}; do
            echo "* Duplicate Src File : $ddd"
        done
    fi

    for n in ${MODULE_ARR[*]}; do
        src_file=$(echo $n | awk -F ":::" '{print $1}')
        compiled_lib=$(echo $n | awk -F ":::" '{print $2}')
        phone_dir=$(echo $n | awk -F ":::" '{print $3}')

        if [ "$compiled_lib" == "" ]; then
            echo "* no .so or .jar etc. was generated from below file or dir. pls check it."
            echo "$src_file"
            continue
        fi

        echo
        echo "* Project name           : $PROJECT_NAME"
        echo "* Src File or Dir        : $src_file"
        echo "* File Got From Compile  : $compiled_lib"

        if [ "$B_ADB_REMOUNT" == "false" ]; then
            echo "* adb remount fail. pls push libs manually. using below command: "
            echo "sudo adb push $PROJECT_PATH/$compiled_lib $phone_dir"
            echo
            continue
        else
            echo
            echo "pushing ..."
            echo "$PROJECT_PATH/$compiled_lib  -->  $phone_dir"
            echo
        fi

        if [ -n "$compiled_lib" -a -n "$phone_dir" ]; then
            $ADB push $PROJECT_PATH/$compiled_lib $phone_dir
        else
            echo "* empty string caused by unknown error."
            echo
            ERROR_NUM=3
            return
        fi
    done
}

#====================================
#process args and opts

#process options
function ProcessOptions() {
    while getopts ":txdul:" opt; do
        DEBUG echo "opt: $opt"
        case "$opt" in
            "t")
                B_TOUCH_ENABLED="true"
                ;;
            "x")
                B_PUSH_TO_PHONE="false"
                ;;
            "u")
                B_UPDATE_API="true"
                ;;
            "d")
                DEBUG="true"
                ;;
            "l")
                DEBUG echo "DEBUG, ProcessParam, LUNCH_NUM=$LUNCH_NUM"
                if [ $(IsInterger $OPTARG) == "true" ]; then
                    LUNCH_NUM=$OPTARG
                else
                    echo "* you need to specify a digit value for option -l"
                    ShellHelp
                    ERROR_NUM=1
                    exit $ERROR_NUM
                fi
                ;;
            "?")
                #Unknown option
                echo "* unknown option: $opt"
                ShellHelp
                ERROR_NUM=1
                exit $ERROR_NUM
                ;;
            ":")
                #an option needs a value, which, however, is not presented
                echo "* option $OPTARG needs a value, but it is not presented"
                ShellHelp
                ERROR_NUM=1
                exit $ERROR_NUM
                ;;
            *)
                #unknown error, should not occur
                echo "* unknown error while processing options and params"
                ShellHelp
                ERROR_NUM=1
                exit $ERROR_NUM
                ;;
        esac
    done
    return $OPTIND
}

#process params
function ProcessParams() {
    DEBUG echo "params: $@"
    if [ $# -eq 0 ]; then
        #no params, just print help
        ShellHelp
        #return 1 here to avoid father operation such as "adb reboot"
        exit 1
    fi

    local pj_root
    local pj_name
    for param in $@; do
        #process and store params into array
        if [ -e $param ]; then
            local full_path=$(readlink -f $param)
            pj_root=$(LocateProjectRoot $full_path)
            DEBUG echo "+++++++++++++++++++++++++++"
            DEBUG echo "ProcessParams, param     : $param"
            DEBUG echo "ProcessParams, pj_root   : $pj_root"
            DEBUG echo "+++++++++++++++++++++++++++"
            
            if [ "$pj_root" == "/" ]; then
                echo
                echo "* target dir is not in any project!"
                ShellHelp
                ERROR_NUM=1
                exit $ERROR_NUM
            fi

            if [ "$PROJECT_NAME" == "" ]; then
                PROJECT_NAME=$(basename $pj_root)
                PROJECT_PATH=$pj_root
            else
                if [ ! "$pj_root" == "$PROJECT_PATH" ]; then
                    echo
                    echo "* for now we do not support compile in different projects. pls compile them one by one."
                    ERROR_NUM=6
                    exit $ERROR_NUM
                fi
            fi
            SrcArrAdd $param

        else
            echo "* param does not exist! $param"
            ShellHelp
            ERROR_NUM=1
            exit $ERROR_NUM
        fi
    done
}

#====================================
#main

ProcessOptions "$@"
param_start=$?
ProcessParams "${@:$param_start}"
unset param_start
    
SetupEnv
DEBUG PrintVariableInfo

DecipherCompileInfo

echo
echo "--------------------------------------------------------------------------------"
echo

if [ "$B_PUSH_TO_PHONE" == "true" ]; then
    ReadyADBRemount
    PushToPhone
else
    JustShowInfo
fi

exit $ERROR_NUM

另: 这个脚本成型于4年前,很多地方处理的并不成熟,
比如找 git 项目根目录的办法
比如 sed 命令的使用
比如 true false 变量的处理
等等等等. 即使是截止到写这篇文章的时间点, 题主的bash水平依然有很多不足之处.
说人话就是: 学无止境,欢迎指教,和平讨论~.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容