1. 程式人生 > >使用shell指令碼實現客戶端應用自動化打包——mac

使用shell指令碼實現客戶端應用自動化打包——mac

由於公司業務拓展,需要實現客戶定製化,即自己上傳必要的檔案即可生成自己的安裝包,這就需要實現自動化打包,打包指令碼選擇了shell指令碼,可能會有人問為什麼不用python呢?因為不會python啊大哭

作為一個前端開發人員,讓寫自動化指令碼,最開始還是一臉懵逼的,但是沒辦法啊,還是得硬著臉皮上啊,於是到網上找各種資料,學習了一下shell常用命令,然後便開始研究了,因為平時mac打包用的是package工具,所以得整理一下使用工具打包的過程是怎樣的,然後再思考如何用指令碼來替代工具所做的步驟。客戶端開發的框架是用的nw.js+angular,所以得在nw.js的打包原理上入手。終於在github找到了開源專案

nwjs-shell-builder。研究了一天指令碼,然後就嘗試著開始自己打包了。

首先需要做的就是配置檔案config.json,該檔案是配置打包時需要進行替換的相關資訊

其中name欄位是用來定義安裝包的名稱,applicationName是用來配置應用名稱,version是用來配置應用版本,src表示的是原始碼的路徑,nwjsVersion表示的是nwjs的版本,osxIconPath表示的是應用圖示,faviconIconPath表示的是工作列圖示,license表示的是使用者許可協議,CFBundleIdentifier表示的bundleID,後面的則表示在應用中需要替換的樣式,應用logo等。

接下來就是開始打包了,首先獲取配置檔案的路徑,並根據配置檔案中的key獲取對應的value,定義臨時打包資料夾,安裝包存放位置,以下就是打包的指令碼,給該指令碼命名為package.sh

#!/usr/bin/env bash
# Exit on error
set -e
BUILD_DIR=`pwd`
WORKING_DIR="${BUILD_DIR}/TMP"
RELEASE_DIR="${BUILD_DIR}/releases"
DIST_DIR="${RELEASE_DIR}/build"
# 配置檔案config.json的路徑
CONFIG_FILE="${BUILD_DIR}/config.json"
# 根據key獲取value方法封裝
get_value_by_key() {
    JSON_FILE=${CONFIG_FILE}
    KEY=${1}
    # REGEX="\"([^\"]*)\""
    JSON_VALUE=$(cat ${JSON_FILE} | jq .${KEY} | sed 's/\"//g')
    echo "${JSON_VALUE}"
}
# Name of your package
architechture="x64"
# architechture="ia32"
pack_osx () {
    for arch in ${architechture[@]}; do
        # 進入到TMP資料夾
        cd ${WORKING_DIR}
       
        mkdir -p ${WORKING_DIR}/build_osx/root/Applications
        # 把app檔案複製到applications中去/
        cp -R "${WORKING_DIR}/osx-${arch}/latest-git/$(get_value_by_key applicationName).app" ${WORKING_DIR}/build_osx/root/Applications/
        # 修改distribution.xml和componts.plist中的內容
        cp ${BUILD_DIR}/resources/osx/components.plist ${RELEASE_DIR}
        cp ${BUILD_DIR}/resources/osx/distribution.xml ${RELEASE_DIR}
        #替換license中的應用名稱
        # cp ${BUILD_DIR}/resources/osx/License.rtf ${RELEASE_DIR}
        cp ${BUILD_DIR}/resources/osx/index.html ${RELEASE_DIR}
        sed -i.bak "s/PengCloud.app/$(get_value_by_key applicationName).app/g" ${RELEASE_DIR}/components.plist
        sed -i.bak "s/com.PengCloud.app/$(get_value_by_key CFBundleIdentifier)/g" ${RELEASE_DIR}/distribution.xml
        sed -i.bak "s/PengCloud.pkg/$(get_value_by_key name).pkg/g" ${RELEASE_DIR}/distribution.xml

    ( cd ${WORKING_DIR}/build_osx/root/Applications && chmod -R a+xr $(get_value_by_key applicationName).app )
        local COUNT_FILES=$(find ${WORKING_DIR}/build_osx/root | wc -l)
        local INSTALL_KB_SIZE=$(du -k -s ${WORKING_DIR}/build_osx/root | awk '{print $1}')

        codesign -f --verbose=4 -s "Developer ID Application: Beijing Xiaopeng Education Technology Development Co. Ltd. (3QN2QHQC4Q)" ${WORKING_DIR}/build_osx/root/Applications/$(get_value_by_key applicationName).app --deep --timestamp=none

        pkgbuild  --root ${WORKING_DIR}/build_osx/root/Applications/ --component-plist ${RELEASE_DIR}/components.plist --identifier $(get_value_by_key CFBundleIdentifier) --install-location /Applications ${RELEASE_DIR}/$(get_value_by_key name).pkg

        productbuild --distribution ${RELEASE_DIR}/distribution.xml  --resources ${RELEASE_DIR} --package-path ${RELEASE_DIR}/ ${DIST_DIR}/$(get_value_by_key name)-$(get_value_by_key version).pkg

        productsign --sign "Developer ID Installer: Beijing Xiaopeng Education Technology Development Co. Ltd. (3QN2QHQC4Q)" ${DIST_DIR}/$(get_value_by_key name)-$(get_value_by_key version).pkg  ${DIST_DIR}/$(get_value_by_key name)-$(get_value_by_key version)-sign.pkg --timestamp=none
    done;
}
build() {
    # if [[ `check_dependencies` = "NO" ]]; then
    #     printf "\nNOTE! NSIS or ImageMagick is missing in the system\n\n";
    #     exit 1;
    # fi
    if [[ ! -f "${WORKING_DIR}" ]]; then
    mkdir -p TMP
    fi
    if [[ ! -d "${RELEASE_DIR}" ]]; then
        mkdir ${RELEASE_DIR}
    fi
    if [[ ! -d "${DIST_DIR}" ]]; then
        mkdir ${DIST_DIR}
    fi
    # 判斷是否存在資原始檔,如果不存在則下載
    if [[ ! -d "$(get_value_by_key src)" ]]; then
        echo "=========bucunzai"
        git clone -b customized gitv:desktop $(get_value_by_key src)
        rm -rf ./resources/.git
    fi
    # 然後對資原始檔中需要定製化的檔案進行替換
    # 替換應用圖示
    cp -rf $(get_value_by_key indexBackground) $(get_value_by_key src)/img/images/pengBG.png
    # 替換工作列圖示

    cp -rf $(get_value_by_key faviconIconPath) $(get_value_by_key src)/configuration/favicon.png
    # 替換首頁背景
    cp -rf $(get_value_by_key indexBackground) $(get_value_by_key src)/img/images/pengBg.png
    # 替換首頁logo
    cp -rf $(get_value_by_key indexLogo) $(get_value_by_key src)/img/images/pengLogo.png
    # 替換會議室背景
    cp -rf $(get_value_by_key conferenceBackground) $(get_value_by_key src)/configuration/themes/default/background1.png
    
    # 替換package.json檔案中的name和productName屬性
    sed -i "" 's/\("name": "\).*/\1'"$(get_value_by_key name)"'",/g' $(get_value_by_key src)/package.json
    # sed -i "" 's/\("productName": "\).*/\1'"$(get_value_by_key applicationName)"'",/g' $(get_value_by_key src)/package.json
    #替換中英文語言包中的應用名稱
    sed -i "" 's/\("IDS_PRODUCT_NAME": "\).*/\1'"$(get_value_by_key applicationName)"'",/g' $(get_value_by_key src)/configuration/languages/cn-cn.json
    sed -i "" 's/\("IDS_PRODUCT_NAME": "\).*/\1'"$(get_value_by_key name)"'",/g' $(get_value_by_key src)/configuration/languages/en-us.json
    ${BUILD_DIR}/nwjs-build.sh \
        --name=$(get_value_by_key name) \
        --application=$(get_value_by_key applicationName) \
        --version=$(get_value_by_key version) \
        --src=$(get_value_by_key src) \
        --nw=$(get_value_by_key nwjsVersion) \
        --osx-icon=$(get_value_by_key osxIconPath) \
        --favicon-icon=$(get_value_by_key faviconIconPath) \
        --CFBundleIdentifier=$(get_value_by_key CFBundleIdentifier) \
        --indexBackground=$(get_value_by_key indexBackground) \
        --indexLogo=$(get_value_by_key indexLogo) \
        --conferenceBackground=$(get_value_by_key conferenceBackground) \
        --target="${1}" \
        --libudev \
        --build
    cd ${BUILD_DIR}
}
clean() {
    if [[ ${1} = "all" ]];then
        rm -rf ${RELEASE_DIR}; printf "\nCleaned ${RELEASE_DIR}\n\n";
    fi

    rm -rf ${WORKING_DIR}; printf "\nCleaned ${WORKING_DIR}\n\n";
}
# TODO maybe deal with cmd switches or leave it all in the config.json file

if [[ ${1} = "--osx" ]]; then
    clean "all";
    build "5";
    pack_osx;
fi
我將生成.app檔案的指令碼分離出來了,命名為nwjs-build.sh,內容如下:
#!/usr/bin/env bash
WORKING_DIR="$(cd -P -- "$(dirname -- "$0")" && pwd -P)"
# default is "FALSE"
LOCAL_NW_ARCHIVES_MODE=true
LOCAL_NW_ARCHIVES_PATH="${WORKING_DIR}/nwjs_download_cache"

# Default nwjs version
NW_VERSION="0.23.7"

# Base domain for nwjs download server
DL_URL="http://dl.nwjs.io"

# This directory will be auto created
TMP="TMP"

# Build target(s)
# 0 - linux-ia32
# 1 - linux-x64
# 2 - win-ia32
# 3 - win- 
# 4 - osx-ia32
# 5 - osx-x64
TARGET="0 1 2 3 4 5"

# Final output directory (relative to current directory where this script running from)
RELEASE_DIR="${WORKING_DIR}/${TMP}/output"

# Date on the package archive as PkgName-YYYYMMDD-OS-architecture.zip
DATE=$(date +"%Y%m%d")
# Name of your package

# Package version


# Handle libudev on linux
LIBUDEV_HANDLER=false


ARR_OS[0]="linux-ia32"
ARR_OS[1]="linux-x64"
ARR_OS[2]="win-ia32"
ARR_OS[3]="win-x64"
ARR_OS[4]="osx-ia32"
ARR_OS[5]="osx-x64"

ARR_DL_EXT[0]="tar.gz"
ARR_DL_EXT[1]="tar.gz"
ARR_DL_EXT[2]="zip"
ARR_DL_EXT[3]="zip"
ARR_DL_EXT[4]="zip"
ARR_DL_EXT[5]="zip"

ARR_EXTRACT_COMMAND[0]="tar"
ARR_EXTRACT_COMMAND[1]="tar"
ARR_EXTRACT_COMMAND[2]="zip"
ARR_EXTRACT_COMMAND[3]="zip"
ARR_EXTRACT_COMMAND[4]="zip"
ARR_EXTRACT_COMMAND[5]="zip"

TXT_BOLD="\e[1m"
TXT_NORMAL="\e[1m"
TXT_RED="\e[31m"
TXT_BLUE="\e[34m"
TXT_GREEN="\e[32m"
TXT_YELLO="\e[93m"
TXT_RESET="\e[0m"
TXT_NOTE="\e[30;48;5;82m"

usage() {
cat <<EOF

LICENSE

    MIT

$(cat ${WORKING_DIR}/LICENSE)

EOF
}


NOTE () {
    printf "\n";
    printf "${TXT_NOTE} ${1} ${TXT_RESET} "
    printf "\n";
}

upper_case_word() {
    word=${1}
    therest=$(tr '[a-z]' '[A-Z]'<<<"${word:0:1}")
    echo "${therest}${word:1}"
}

clean() {
    rm -rf ${WORKING_DIR}/${TMP};
    NOTE "Removed \"${WORKING_DIR}/${TMP}\" directory and it's content";
}

extractme() {
    if [[ ${1} = "zip" ]]; then
        unzip -qq ${2} -d ${3};
    else
        tar xzf ${2} -C ${3};
    fi
}

split_string() {
	#USAGE: `split_string $string ,` - the comma here is the separator. Also see `man cut`
	echo "$1" | cut -d"$2" -f1;
}

make_bins() {
    # 建立一個release資料夾
    mkdir -p ${RELEASE_DIR}
    local make_os=`split_string "${1}" "-"`
    echo "========ddd++++++${1}"
    if [[ ${make_os} = "win" ]]; then
        mk_windows ${1};
    elif [[ ${make_os} = "osx" ]]; then
        mk_osx ${1};
    else
        printf "\nNo such target\n";
        exit 1;
    fi
}

mk_osx() {
    
    cp -R ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/nwjs/*.app ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app;
    cp -R ${WORKING_DIR}/${TMP}/osx-x64/latest-git/${APPLICATION_NAME}.nw ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app/Contents/Resources/app.nw;
    rm -r ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.nw

    if [[ -f "${OSX_RESOURCE_ICNS}" ]];then        
        cp -r ${OSX_RESOURCE_ICNS} ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app/Contents/Resources/
    else
        OSX_RESOURCE_ICNS="${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/nwjs/node-webkit.app/Contents/Resources/nw.icns"
    fi
    ICON_NAME=$(echo "${OSX_RESOURCE_ICNS}" | rev | cut -d"/" -f1 | rev)

    sed -i.bak "s/app.icns/${ICON_NAME}/g" ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app/Contents/Info.plist
    sed -i.bak "s/io.nwjs.nwjs/${CFBundleIdentifier}/g" ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app/Contents/Info.plist
    sed -i.bak "s/3071.115/${PKG_VERSION}/g" ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app/Contents/Info.plist
    rm ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.app/Contents/Info.plist.bak


    cd ${WORKING_DIR}/${TMP}/${1}/latest-git
    zip -qq -r ${APPLICATION_NAME}-${DATE}-${1}.zip *;
    mv ${APPLICATION_NAME}-${DATE}-${1}.zip ${RELEASE_DIR};
    cd ${WORKING_DIR};
} 

build() {
    for i in ${TARGET}; do
        mkdir -p ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git
        mkdir -p ${LOCAL_NW_ARCHIVES_PATH}
        NOTE 'WORKING'
        printf "Bulding ${TXT_BOLD}${TXT_YELLO}${APPLICATION_NAME}${TXT_RESET} for ${TXT_BOLD}${TXT_YELLO}${ARR_OS[$i]}${TXT_RESET}\n"
        # 這裡遍歷之後為什麼是上層資料夾目錄
        # for DL_FILE in ${LOCAL_NW_ARCHIVES_PATH}/nwjs-sdk-v${NW_VERSION}-${ARR_OS[$i]}.${ARR_DL_EXT[$i]}; do
        for DL_FILE in ${LOCAL_NW_ARCHIVES_PATH}/*-v${NW_VERSION}-${ARR_OS[$i]}.${ARR_DL_EXT[$i]}; do
        
            printf "File ${TXT_YELLO}nwjs-${NW_VERSION}-${ARR_OS[$i]}.${ARR_DL_EXT[$i]}${TXT_RESET} is in the download cache\n- no need to re-download\n"
            cp ${LOCAL_NW_ARCHIVES_PATH}/nwjs-sdk-v${NW_VERSION}-${ARR_OS[$i]}.${ARR_DL_EXT[$i]} ${WORKING_DIR}/${TMP}/${ARR_OS[$i]};

            # ARR_EXTRACT_COMMAND:zip  extractme這個方法的作用是解壓檔案放到指定目錄,第一個引數是壓縮檔案的格式,第二個引數是壓縮檔案,第三個引數是壓縮後放置的位置
            extractme "${ARR_EXTRACT_COMMAND[$i]}" "${DL_FILE}" "${WORKING_DIR}/${TMP}/${ARR_OS[$i]}";
            # mkdir -p ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/nwjs
            mv ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/*-v${NW_VERSION}-${ARR_OS[$i]} ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/nwjs;
            
            # # 如果是mac系統,將自己的程式碼拷貝到.nw資料夾中
            if [[ `split_string "${ARR_OS[$i]}" "-"` = "osx" ]]; then
                cp -r ${PKG_SRC} ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.nw;
            else
                # 如果不是,將自己的程式碼壓縮放到.nw資料夾中
                cd ${PKG_SRC};
                zip -qq -r ${APPLICATION_NAME}.zip *;
                mv ${APPLICATION_NAME}.zip ${WORKING_DIR}/${TMP}/${ARR_OS[$i]}/latest-git/${APPLICATION_NAME}.nw;
                cd ${WORKING_DIR};
            fi
            # Build binaries
            make_bins "${ARR_OS[$i]}";
        done
    done
    NOTE "DONE";
    printf "You will find your '${APPLICATION_NAME}' builds in '${RELEASE_DIR}' directory\n";
}

### Arguments
while true; do
  case $1 in
    -h | --help )
        clear > /dev/null;
        usage | less;
        exit 0
        ;;
    --nw=* )
        NW_VERSION="${1#*=}";
        shift
        ;;
    --name=* )
        PKG_NAME="${1#*=}";
        shift
        ;;
    --application=* )
        APPLICATION_NAME="${1#*=}";
        shift
        ;;
    --version=* )
        PKG_VERSION="${1#*=}";
        shift
        ;;
    --output-dir=* )
        RELEASE_DIR="${1#*=}";
        shift
        ;;
    --src=* )
        PKG_SRC="${1#*=}"
        shift
        ;;
    --target=* )
        TARGET="${1#*=}"
        shift
        ;;
    --osx-icon=* )
        OSX_RESOURCE_ICNS="${1#*=}"
        shift
        ;;
    --favicon-icon=* )
        OSX_FAVICON="${1#*=}"
        shift
        ;;
    --CFBundleIdentifier=* )
        CFBundleIdentifier="${1#*=}"
        shift
        ;;
    --indexBackground=* )
        indexBackground="${1#*=}"
        shift
        ;;
    --indexLogo=* )
        indexLogo="${1#*=}"
        shift
        ;;
    --conferenceBackground=* )
        conferenceBackground="${1#*=}"
        shift
        ;;
    --clean )
        clean;
        exit 0
        ;;
    --local )
        LOCAL_NW_ARCHIVES_MODE=true
        shift
        ;;
    --libudev )
        LIBUDEV_HANDLER=true
        shift
        ;;
    --build )
        build;
        exit 0
        ;;
    -- )
        shift;
        break
        ;;
    -* )
        printf 'Hmmm, unknown option: "%s".\n' "${1}";
        exit 0
        ;;
    * )
        usage;
        break
        ;;
  esac
done

到這裡為止,一個完整的打包指令碼就出爐了,接下來就是執行指令碼進行自動化打包了。其中具體的路徑就需要我們根據自己的需要來進行配置了。進入到package.sh所在的檔案目錄,開啟終端,執行命令./pack.sh --osx,接下來就坐等生成的pkg安裝包了。第一次執行的時候會報錯,直接加上chomd +x package.sh,然後再執行就可以了。當然,能一下子把指令碼貼出來肯定是在完全實現之後了,在研究過程中也是遇到很多很多各種各樣的問題,畢竟是第一次接觸shell,有時候甚至能在問題上卡住好久好久,不過好在結果是美好的。不過指令碼肯定還有很多需要優化的地方,有什麼建議或者問題也歡迎指正。