p3c简介、GitLab集成p3c-pmd
GitLab集成p3c-pmd
简介
p3c
2017年,阿里公开了第一版的java代码规范手册
代码规范检查插件p3c,是根据《阿里巴巴Java开发手册》转化而成的自动化插件
PMD-源码分析器
PMD (Programming Mistake Detector) 是一个开源的静态代码检查工具
静态代码检查就是在不运行代码的情况下检查代码中的内容,然后和事先制定的规则进行比对,指出代码中不符合规则的部分
PMD 支持多种语言,比如项目本身的 Java,Salesforce 相关的 Apex、VisualForce,还有 JavaScript、XML 等
PMD 对于各种语言都预设了一套代码规则,也支持开发自定义规则,可以使用 Java 类或者 XPath
PMD 的工作逻辑
PMD 是一款采用 BSD 协议发布的Java 程序静态代码检查工具
当使用PMD规则分析Java源码时,PMD首先利用JavaCC和EBNF文法产生了一个语法分析器,用来分析普通文本形式的Java代码,产生符合特定语法结构的语法,同时又在JavaCC的基础上添加了语义的概念即JJTree,通过JJTree的一次转换,这样就将Java代码转换成了一个AST(抽象语法树),AST是Java符号流之上的语义层,PMD把AST处理成一个符号表。然后编写PMD规则,一个PMD规则可以看成是一个Visitor,通过遍历AST找出多个对象之间的一种特定模式,即代码所存在的问题
抽象语法树是将代码结构通过“树”的形式展现出来,每部分代码(类、变量声明、if-else 语句、变量赋值、数据库查询等)会成为树的各个节点
在代码转化为语法树后,PMD 中制定的规则会检测树的相应节点,分析其属性或结构,从而找出违反规定的部分
p3c-pmd
p3c-pmd 插件是基于 PMD 实现的,更具体的来说是基于 pmd-java 的,因为 PMD 不仅支持 Java 代码分析,还支持其他多种语言
具体自定义规则的方式,通过自定义Java类和XPATH规则实现
GitLab集成p3c-pmd
GitLab hook
GitLab服务端有三个主要的钩子
1、pre-receive:处理客户端push动作时最先被调用的脚本,以非0值退出拒绝Push,可以用来做注释标准化、代码标准化等)
2、update:与pre-receive功能类似,为每个准备更新的分支各运行一次,假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个被推送的分支各运行一次。它不会从标准输入读取内容,而是接受三个参数:引用的名字(分支),推送前的引用指向的内容的 SHA-1 值,以及用户准备推送的内容的 SHA-1 值。 如果 update 脚本以非零值退出,只有相应的那一个引用会被拒绝;其余的依然会被更新
3、post-receive:是在push之后执行的脚本,可以用来调用后续的持续集成、发邮件通知等。它接受与 pre-receive 相同的标准输入数据,该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态, 所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间
更多变量详情请参考官方文档
GitLab hook 可用的环境变量(所有hook)
环境变量 | 描述 |
---|---|
GL_ID | 发起推送的用户的 GitLab 标识符。例如,user-2234 |
GL_PROJECT_PATH | (GitLab 13.2 及更高版本)GitLab 项目路径 |
GL_PROTOCOL | (GitLab 13.2 and later) Protocol used for this change. One of: http (Git push using HTTP), ssh (Git push using SSH), or web (all other actions) |
GL_REPOSITORY | project- |
GL_USERNAME | 发起推送的用户的 GitLab 用户名 |
GitLab hook 可用的环境变量(仅限pre-receive、post-receive)
环境变量 | 描述 |
---|---|
GIT_ALTERNATE_OBJECT_DIRECTORIES | 隔离环境中的备用对象目录 |
GIT_OBJECT_DIRECTORY | 隔离环境中的 GitLab 项目路径 |
GIT_PUSH_OPTION_COUNT | push的数量 |
GIT_PUSH_OPTION_ | Value of push options where i is from 0 to GIT_PUSH_OPTION_COUNT - 1 |
一、获取p3c-pmd
下载源码
进入p3c-pmd子项目,执行 mvn clean package 进行打包,获取到jar包
二、配置GitLab hook
2.1、项目单独配置
2.1.1、获取项目路径
登录GitLab页面,在 管理中心 -> 项目 找到相应的项目,获取到项目的Gitaly相对路径(例如:@hashed/fc/09/fc091d39524c9d4b5b11f84f9132996a94ca01c9816d2db3b866bef1b0699d91.git)
2.1.2、配置GitLab hook
登录GitLab服务器,在项目路径下,创建custom_hooks/pre-receive.d目录,并在pre-receive.d目录下创建pre-receive文件
2.2、GitLab全局配置
全局配置路径:/opt/gitlab/embedded/service/gitlab-shell/hooks/pre-receive.d
在路径下创建pre-receive文件
pre-receive 文件详解
思路:
使用pre-receive钩子,确保执行push动作的时候脚本被率先执行
使用 git diff 命令找出两次push之间有差异的 *.java 文件
使用 p3c-pmd 对找出来的文件进行分析,判断是否存在违规现象
根据检测结果,决定是否允许本次push
黑/白名单思路:
(全局模式下)默认所有项目都不启动检测功能,只有检测到项目在黑名单内,才会触发检测动作
检测完成后,如果项目名在项目白名单内、或执行push的用户在用户白名单内,则将检测结果强制改为通过(检测过程中如果检测到违规代码,会显示在标准输出)
黑名单功能与白名单功能可以共存,即检测到push动作,因为在黑名单内,所以会触发检测动作;检测完成之后,因为在白名单内,所以会强制将检测结果改为通过
Git中有个内置变量 GL_REPOSITORY ,可以返回当前项目的项目编号,例如 project-787 ,可以根据这个变量来确定项目,进行黑白名单判断
用户白名单文件示例
xiaoming
xiaohu
项目白名单文件示例
# http://gitlab.com/devops/p3c-test.git
project-787
# http://gitlab.com/devops/p3c.git
project-765
项目黑名单文件示例
# http://gitlab.com/devops/p3c-test.git
project-787
# http://gitlab.com/devops/p3c.git
project-765
pre-receive 文件内容
#!/bin/bash
# oldrev,上次成功提交的push编号
# newrev,本次push的编号
# committer,当前提交代码的用户
# EJECT=0,通过
export JAVA_HOME=/usr/local/jdk-18
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH
export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH
# 命令结果状态
REJECT=0
# 代码检测库
PMD_LIB=/var/opt/gitlab/gitlab-hooks/p3c-pmd-2.1.0-jar-with-dependencies.jar
# 检测结果国际化语言:en、zh
PMD_LANGUAGE=zh
# 文件字符编码
FILE_ENCODING=utf-8
# 黑名单判断
# git项目黑名单
GIT_BLACK_LIST='/var/opt/gitlab/gitlab-hooks/git-black-list.txt'
# 判断项目是否在黑名单内
IS_GIT_BLACK_LIST=`/usr/bin/grep -nw "${GL_REPOSITORY}" $GIT_BLACK_LIST`
# 白名单判断
# git项目白名单
GIT_WHITE_LIST="/var/opt/gitlab/gitlab-hooks/git-white-list.txt"
# 判断项目是否在白名单内
IS_GIT_WHITE_LIST=`/usr/bin/grep -nw "${GL_REPOSITORY}" $GIT_WHITE_LIST`
# 特权用户名单
USER_WHITE_LIST="/var/opt/gitlab/gitlab-hooks/user-white-list.txt"
# 判断是否属于特权用户
IS_USER_WHITE_LIST=`/usr/bin/grep -nw "${GL_USERNAME}" $USER_WHITE_LIST`
# 判断项目是否在黑名单内
if [ -n "$IS_GIT_BLACK_LIST" ]; then
# 输出本次push的信息
echo "开始进行代码质量检测..."
echo "项目编号:${GL_REPOSITORY}"
echo "提交用户:${GL_USERNAME}"
# 获取标准输入
while read oldrev newrev refname; do
#echo "oldrev is ${oldrev}"
#echo "newrev is ${newrev}"
#echo "refname is ${refname}"
# 如果没有上次成功的push,则给oldrev赋一个初始值
if [ "$oldrev" = "0000000000000000000000000000000000000000" ];then
oldrev="${newrev}^"
fi
# 执行git命令,获取两次提交中有差异的*.java文件
files=`git diff --name-only ${oldrev} ${newrev} | grep -e ".java$"`
# 判断是否检测到有差异的*.java文件
if [ -n "$files" ]; then
# echo "开始bbb"
# 定义临时目录
TEMPDIR="tmp"
# 将有差异的文件复制到临时目录下
for file in ${files}; do
mkdir -p "${TEMPDIR}/`dirname ${file}`" > /dev/null
git show $newrev:$file > ${TEMPDIR}/${file}
done;
# 对临时目录中的文件执行p3c-pmd检测
java -Dfile.encoding=$FILE_ENCODING -Dpmd.language=$PMD_LANGUAGE -cp $PMD_LIB net.sourceforge.pmd.PMD -d $TEMPDIR -R /var/opt/gitlab/gitlab-hooks/rulesets/java/all-rule.xml,/var/opt/gitlab/gitlab-hooks/rulesets/java/add-rule.xml -f text -shortnames -no-cache
# 获取检测结果,0代表通过
REJECT=$?
echo "reject = "$REJECT
# 判断检测结果是否通过
if [ $REJECT = 0 ] ;then
echo "码出高效!您的代码通过阿里巴巴Java开发规范(黄山版)检测!"
# 判断用户是否在白名单内,若在,则将检测结果改为通过
elif [ -n "$IS_USER_WHITE_LIST" ]; then
echo "特权用户,允许提交!"
REJECT=0
# 判断项目是否在白名单内,若在,则将检测结果改为通过
elif [ -n "$IS_GIT_WHITE_LIST" ]; then
echo "特殊项目,允许提交!"
REJECT=0
else
echo "在您的代码中发现了瑕疵,请及时修复,然后再提交!(代码规范请参考:https://github.com/alibaba/p3c)"
fi
# 删除临时目录,释放磁盘空间,防止污染下次检测
rm -rf $TEMPDIR
else
echo "本次提交无需检测"
fi
done
fi
# 将检测结果作为脚本的返回码
exit $REJECT