GitLab服務端集成CheckStyle,PMD實現代碼自動靜態檢查

之前寫過一篇 SVN服務端集成Checkstyle實現代碼自動靜態檢查。最近部門打算試點GIT作爲SCM,因此需要將相應的服務端檢測功能進行一次遷移,於是就有了本篇博文。

1. 概述

對於Git通過Hook實現靜態代碼檢測,大致分爲兩個方向:

  1. 藉助Client-Side-Hook來實現。此方法對應於研發人員工作機上的${PROJECT_ROOT}/.git/hooks/pre-commit腳本實現。
  2. 藉助Server-Side-Hook來實現。此方法對應於Git服務端的/var/opt/gitlab/git-data/repositories/<group>/<project>.git/custom_hooks/pre-receive腳本。

相比較之下,兩種實現方式之中,參考資料豐富度層面前一種勝出,但筆者所在部門希望儘可能地促使研發人員將問題原地解決,因此我們最終決定求助於第二種方案 —— Server-Side-Hook。

2. Server-Side-Hook

關於Server-Side-HookGitLab - Server_Hooks已經有了詳盡的介紹,這裏我們不出意外地將目光集中到了 pre-receive

2.1 準備檢測工具

準備CheckStyle/PMD相關JAR和XML規則文件。

  1. CheckStyle - JAR
  2. PMD - JAR

將下載的JAR和對應的XML推送到服務器對應位置,筆者這裏是直接放到了對應項目根目錄下:

  1. /var/opt/gitlab/git-data/repositories/<group>/<project>.git/checkstyle
  2. /var/opt/gitlab/git-data/repositories/<group>/<project>.git/pmd-bin-6.20.0
2.2 創建pre-receive腳本文件

首先我們需要在對應位置創建一個空白的pre-receive文件(注意該文件沒有後綴)。

# 切換到服務端項目目錄下
cd /var/opt/gitlab/git-data/repositories/<group>/<project>.git
mkdir custom_hooks
cd custom_hooks
vi pre-receive

接下來就是重頭戲。

2.3 pre-receive腳本內容

注意這裏的腳本實現方式相當粗暴,因爲筆者打算借鑑自其它產品來對此類功能做一次封裝,因此這裏的要求只是生效即可。

#!/usr/bin/env bash

# From https://bbs.csdn.net/topics/392558612

#加載環境變量, 讓下面的java指令生效
source /etc/profile

# 創建臨時目錄
TMP_DIR=$(mktemp -d)

# CheckStyle
CHECKSTYLE_BASE_PATH="/var/opt/gitlab/git-data/repositories/<group>/<project>.git/checkstyle"
CHECKSTYLE_JAR_FILE="$CHECKSTYLE_BASE_PATH/checkstyle-8.17-all.jar"
CHECKSTYLE_XML_FILE="$CHECKSTYLE_BASE_PATH/checkstyle-OLD.xml"

# PMD
PMD_BASE_PATH="/var/opt/gitlab/git-data/repositories/<group>/<project>.git/pmd-bin-6.20.0"
PMD_CLASSPATH="$PMD_BASE_PATH/lib/*"
PMD_XML_FILE="$PMD_BASE_PATH/kanq-pmd-bestpratice-OLD.xml"

# 錯誤數
errors_count=0
 
# 空hash
EMPTY_REF='0000000000000000000000000000000000000000'
 
while read oldrev newrev ref 
do
    # 當push新分支的時候oldrev會不存在,刪除時newrev就不存在
    if [[ $oldrev != $EMPTY_REF && $newrev != $EMPTY_REF ]]; then
        # 被檢查了的文件數
        checked_file_count=0
        # 找出哪些文件被更新了
        for line in $(git diff-tree -r $oldrev..$newrev | grep -oP '.*\.(java)' | awk '{print $5$6}')
        do
            # 文件狀態. 本地查詢命令: git status --help
            # D: deleted
            # A: added
            # M: modified
            status=$(echo $line | grep -o '^.')
            # 不檢查被刪除的文件
            if [[ $status == 'D' ]]; then
                continue
            fi
 
            # 文件名
            file=$(echo $line | sed 's/^.//')
            # 爲文件創建目錄
            mkdir -p $(dirname $TMP_DIR/$file)
            # 保存文件內容
            git show $newrev:$file > $TMP_DIR/$file
            output=$(java -jar $CHECKSTYLE_JAR_FILE -c $CHECKSTYLE_XML_FILE  $TMP_DIR/$file)
            outputPMD=$(java -classpath "$PMD_CLASSPATH" net.sourceforge.pmd.PMD -R $PMD_XML_FILE -dir $TMP_DIR)            
            # 以下命名管道是爲了將形如"Checkstyle ends with 89 errors."字符串中的 89 找出來
            error=$(echo $output | grep -oP '([0-9]+) errors' | grep -oP '[0-9]+')
            errorPMD=$(echo $outputPMD | grep -oP '[0-9]{2,}' | wc -l)
           
            if [[ $errorPMD || $error ]]; then
 
                msg="${file}: "
 
                if [[ $errorPMD > 0 ]]; then
                    let "warnings_count = warnings_count + 1"
                    echo $outputPMD
                    rm -rf $TMP_DIR
                    exit 1
                fi
                if [[ $error > 0 ]]; then
                    msg="$msg has checkstyle [${error}] errors"
                    echo $output
                    rm -rf $TMP_DIR
                    exit 1  
                    let "errors_count = errors_count + 1"
                fi
 
                echo -e "    $msg"
            fi
 
            let "checked_file_count = checked_file_count + 1";
 
        done
 
       if [[ $checked_file_count == 0 ]]; then
           echo -e " No file was checked."
       elif [[ $warnings_count == 0 && $errors_count == 0 ]]; then
           echo -e "'Congratulations!!! no error, no warning'"
       elif [[ $errors_count  == 0 ]]; then
           echo -e " Good job, no errors were found!!!"
       fi
         
    fi
done
 
# 刪除臨時目錄
rm -rf $TMP_DIR

if [[ $warnings_count > 0 || $errors_count > 0 ]]; then
    echo 'check fail. please check yourself local'
    exit 1
fi


exit 0

3. 效果

檢測失敗效果圖
錯誤提示不如 SVN集成PMD實現代碼自動靜態檢查 有條理,但有個消息是"錯誤提示的行數正常了"。

4. 建議

  1. 可以考慮使用global server hook,然後針對特例再作 single repository server hook。
  2. 鑑於SVN和GIT設計思路的不同,在使用GIT時候如果出現檢測失敗,修復之後一定要記得再commit一次,然後再次push。其它方式還有建立Client Site Hook - pre commit 以及 取消本地commit。

5. Links

  1. SVN集成Checkstyle實現代碼自動靜態檢查
  2. Atlassian - Bitbucket - Server-Side-Hooks 筆者個人認爲相較於下面的文檔,Atlassian文檔至少在這一點上更爲豐富。
  3. GitLab - Server_Hooks
  4. Git - Server-Side-Hooks
  5. Git 使用 pre commit 實現代碼自動靜態檢查 //需要BASH支持
  6. CheckStyle Git hook 簡單配置 // 需要Python支持
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章